问题
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