Why do I get this NullReferenceException sometimes?

删除回忆录丶 提交于 2019-12-25 17:16:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!