问题
Here's piece of code where It throws the exception sometimes:
Pendings = ClientCode.PendingOrders.Select(x => new DisplayPending()
{
ItemCode = ClientCode.Items.First(y => y.Id == x.ItemCode).Id,
ItemName = ClientCode.Items.First(y => y.Id == x.ItemCode).Name,
OrderNo = x.BuyOrderNo == 0 ? x.SellOrderNo : x.BuyOrderNo,
OrderType = x.OrderType == OrderType.Buy ? "Buy" : "Sell",
PartyCode = x.PartyCode,
Price = x.Price,
Quantity = x.Quantity
});
and here's the message:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
x was null.
PendingOrders
is anObservableCollection
and It has more than 500 items.
EDIT
Here's how I populate my extended ObservableCollection
:
public class AsyncObsetion<T> : ObservableCollection<T>
{
SynchronizationContext context = SynchronizationContext.Current;
readonly object _lock = new object();
public AsyncObsetion() { BindingOperations.EnableCollectionSynchronization(this, _lock); }
public AsyncObsetion(IEnumerable<T> list) : base(list) { BindingOperations.EnableCollectionSynchronization(this, _lock); }
void RaiseCollectionChanged(object param) => base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
void RaisePropertyChanged(object param) => base.OnPropertyChanged((PropertyChangedEventArgs)param);
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SynchronizationContext.Current == context) RaiseCollectionChanged(e);
else context.Send(RaiseCollectionChanged, e);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (SynchronizationContext.Current == context) RaisePropertyChanged(e);
else context.Send(RaisePropertyChanged, e);
}
public void InsertRange(IEnumerable<T> items)
{
this.CheckReentrancy();
foreach (var item in items)
this.Items.Add(item);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
When Client
connects to Server
, it sends 6 byte[]
which are added in 8 AsyncObsetion<T>
named Items
, NewsCollection
, Issued
, BuyOrders
, SellOrders
, PendingOrders
, ExecutedOrders
and MyExecutedOrders
on client side. This piece of code is responible for handling those byte[]
which client gets from server:
var tasks = new List<Task>();
tasks.Add(Task.Run(() =>
{
for (int i = 0; i < header.ItemSize; i += Constants.itemSize)
{
var item = PacMan<ItemStruct>.Unpack(itemArray.Skip(i).Take(Constants.itemSize).ToArray());
Items.Add(new Item()
{
Id = item.Id,
Name = item.Name,
Cap = item.Cap,
Floor = item.Floor,
Mid = item.Floor + ((item.Cap - item.Floor) / 2),
Securities = item.Securities,
Owners = item.Owners,
Govt = item.Govt,
Institutions = item.Institutions,
Foreign = item.Foreign,
Public = item.Public,
PerSecurity = PacMan<PerSecurityFinancialsStruct>.ArrayToList<PerSecurityFinancials>(item.PerSecurity),
Dividends = PacMan<DividendStruct>.ArrayToList<Dividend>(item.Dividends),
InitialSecurity = item.InitialSecurity
});
}
}).ContinueWith(t =>
{
if (header.HasExecuted) GetExOrders(header.ExecutedSize, execArray);
if (header.HasNews) AddNews(newsArray.ToArray());
}));
tasks.Add(Task.Run(() => { if (header.HasBuy) GetOrders(header.BuySize, buyArray, "buy"); }));
tasks.Add(Task.Run(() => { if (header.HasSell) GetOrders(header.SellSize, sellArray, "sell"); }));
tasks.Add(Task.Run(() => AddIssue(issueArray.ToArray())));
Task.WaitAll(tasks.ToArray());
hasReceivedData = true;
App.Current.Dispatcher.Invoke(() => CommandManager.InvalidateRequerySuggested());
OnConnected();
OrderVM.ItemCode = Items.First().Id;
e.Completed += Receive;
e.SetBuffer(headerBuffer, 0, headerBuffer.Length);
if (!e.AcceptSocket.ReceiveAsync(e)) Receive(null, e);
OnConnected()
is an event and when it's fired client starts the process of creating Pendings
out of PendingOrders
, which is made out of buyArray
and sellArray
. I think here's the problem and I believe somehow OnConnected()
gets fired before Task.WaitAll(tasks.ToArray())
sometimes. If someone is interested, here's what GetOrders
does:
void GetOrders(int size, IEnumerable<byte> array, string type)
{
var orderList = new List<AllOrder>();
var pendingList = new List<AllOrder>();
for (int i = 0; i < size; i += Constants.orderSize)
{
var order = PacMan<AllOrderStruct>.Unpack(array.Skip(i).Take(Constants.orderSize).ToArray());
AddInitNewOrder(order, orderList, pendingList);
}
if (type == "buy") CheckNumberAndAdd(orderList, BuyOrders);
else CheckNumberAndAdd(orderList, SellOrders);
CheckNumberAndAdd(pendingList, PendingOrders);
}
here's AddInitNewOrder
:
void AddInitNewOrder(AllOrderStruct order, List<AllOrder> orders, List<AllOrder> pendingOrders)
{
var o = new AllOrder();
o.Action = order.Action;
o.OrderType = order.OrderType;
o.ItemCode = order.ItemCode;
o.BrokerName = order.BrokerName;
o.PartyCode = order.PartyCode;
o.Price = order.Price;
o.Quantity = order.Quantity;
o.BuyOrderNo = order.BuyOrderNo;
o.SellOrderNo = order.SellOrderNo;
orders.Add(o);
if (o.BrokerName == BrokerName) pendingOrders.Add(o);
}
and here's CheckNumberAndAdd
:
void CheckNumberAndAdd<T>(List<T> normList, AsyncObsetion<T> obsList)
{
var count = normList.Count;
if(count > 50)
{
obsList.InsertRange(normList.Take(count - 50));
var remaining = normList.Skip(count - 50).ToList();
for (int i = 0; i < remaining.Count; i++) obsList.Insert(0, remaining[i]);
}
else for (int i = 0; i < count; i++) obsList.Insert(0, normList[i]);
}
If I set brekpoint in GetOrders
function, I see pendingList
gets 483 items from sellArray
and 494 items from buyArray
, so altogether I've 977 items. So I should get 977 Items in PendingOrders
always BUT If I remove brekpoint in GetOrders
and set breakpoint in Subscribe
method, which is hooked into that event ClientCode.OnConnected += Subscribe;
I see PendingOrders
sometimes gets less than 977 items.
回答1:
As @BionicCode stated, you just have some null elements in the collection.
Just filter them out with .Where(x != null)
or similar.
Although this might solve the problem, generally undetected null references are a hint about week points in the code.
Consider reinforcing your null check policy to avoid these errors in the future. The same framework allows you to perform checks on nullable objects, making the code cleaner and more robust.
https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/february/essential-net-csharp-8-0-and-nullable-reference-types
回答2:
One way to solve the issue is to wrap the last three lines of GetOrders
method like this:
App.Current.Dispatcher.Invoke(() =>
{
if (type == "buy") CheckNumberAndAdd(orderList, BuyOrders);
else CheckNumberAndAdd(orderList, SellOrders);
CheckNumberAndAdd(pendingList, PendingOrders);
});
this works.
来源:https://stackoverflow.com/questions/59470014/why-do-i-get-this-nullreferenceexception-sometimes