Null conditional operator to "nullify" array element existence

11,734

Solution 1

No, because it is a null-conditional operator, not an indexoutofrange-conditional operator and is merely syntactic sugar to something like the following:

int? count = customers?[0]?.Orders?.Count();

if (customers != null && customers[0] != null && customers[0].Orders != null)
{
    int count = customers[0].Orders.Count();
}

You can see that if there is no zeroth customer, you will get your regular IndexOutOfRangeException.

One way you could work around it is to have an extension method that checks for the index and returns null if it doesn't exist:

public static Customer? GetCustomer(this List<Customer> customers, int index)
{
    return customers.ElementAtOrDefault(index); // using System.Linq
}

Then your check could be:

int? count = customers?.GetCustomer(0)?.Orders?.Count();

Solution 2

customers?.FirstOrDefault()?.Orders?.Count();

No zeroeth, no problem.

Solution 3

If you want to get the nth element without having NullReference or IndexOutOfRange exceptions, you can use:

customers?.Skip(n)?.FirstOrDefault()

Solution 4

It doesn't support indexing safety because, when you get down to it, an indexer really is just syntactic sugar for any other type of method.

For example:

public class MyBadArray
{
    public Customer this[int a]
    {
        get
        {
            throw new OutOfMemoryException();
        }
    }
}

var customers = new MyBadArray(); 
int? count = customers?[5]?.Orders?.Count();

Should this be caught here? What if the exception was more sensible, similar to a KeyNotFoundException, but specific to the type of collection we're implementing? We'd have to continually update the ?. functionality to keep up.

Further, ?. does not catch exceptions. It prevents them.

var customer = customers?[5]; is actually compiled as:

Customer customer = null;
if (customers != null)
    customer = customers[5];

Making it catch exceptions becomes exceptionally more difficult. For example:

void Main()
{
    var thing = new MyBadThing(); 
    thing.GetBoss()?.FireSomeone();
}

public class MyBadThing
{
    public class Boss
    {
        public void FireSomeone() 
        { 
            throw new NullReferenceException();
        }
    }
    public Boss GetBoss()
    {
        return new Boss();
    }
}

If it were simply catching exceptions, it would be written as :

Boss boss = customer.GetBoss();
try 
{
    boss.FireSomeone();
} catch (NullReferenceException ex) { 

}

Which would actually catch the exception within FireSomeone, rather than the null reference exception which would be thrown if boss were null.

The same bad-catching problem would be present if we were to catch index lookup exceptions, key not found exceptions, etc.

Share:
11,734

Related videos on Youtube

Michael Sorens
Author by

Michael Sorens

• Software designer/architect, application/website developer, author, educator--see full brand page. • Broad exposure to diverse firms from R&amp;D at a Fortune 500 firm to principal software designer at a tiny startup company. • Worked on projects including: wep apps, content management systems, laser control, multi-user systems, database tools, color laser printer firmware. • Over 100 articles published on Simple-Talk.com and DevX.com covering topics (TDD, code review, source control, documentation, debugging, code smells, usability, visualization, testing) and technologies (C#, PowerShell, .NET, LINQ, JavaScript, AngularJs, XML, WPF, WinForms, database) -see full article list. • Open-source endeavors:   (1) SqlDiffFramework, a database comparison tool for comparing heterogeneous data sources.   (2) DocTreeGenerator, combines help pages of your PS cmdlets into a tree-structured HTML web site.   (3) MonitorFactory, a PowerShell framework to generate near-real-time monitors for any data resources.   (4) XmlDoc2CmdletDoc, generates individual help pages for binary PowerShell cmdlets.   (5) collection of developer tools/libraries in several languages (see API bookshelf); • Taught at community colleges and at University of Phoenix. • Member LinkedIn ( http://www.linkedin.com/in/michaelsorens )

Updated on June 21, 2022

Comments

  • Michael Sorens
    Michael Sorens almost 2 years

    The new C# 6.0 null-conditional operator is a handy vehicle for writing more concise and less convoluted code. Assuming one has an array of customers, then you could get null instead of a length if customers is null using this (examples from MSDN):

    int? length = customers?.Length;
    

    Similarly you could get null instead of a customer with this:

    Customer first = customers?[0];
    

    And for a more elaborate expression, this yields null if customers is null, the first customer is null, or the first customer's Orders object is null:

    int? count = customers?[0]?.Orders?.Count();
    

    But then there is the interesting case of the non-existent customer that the null-conditional operator does not seem to address. We saw above that a null customer is covered, i.e. if an entry in the customers array is null. But that is quite distinct from a non-existent customer, e.g. looking for customer 5 in a 3-element array or customer n in a 0-element list. (Note that the same discussion applies to Dictionary lookup as well.)

    It seems to me that the null-conditional operator is focused exclusively on negating the effects of a NullReferenceException; IndexOutOfRangeException or KeyNotFoundException are alone, exposed, cowering in the corner, and needing to fend for themselves! I submit, that in the spirit of the null-conditional operator, it should be able to handle those cases as well... which leads to my question.

    Did I miss it? Does the null-conditional provide any elegant way to truly cover this expression...

    customers?[0]?.Orders?.Count();
    

    ...when there is no zeroth element?

    • Mephy
      Mephy about 8 years
      "Null-conditional operator", not "exception globbing operator". Personally I'd consider unintuitive behavior if it bypasses OutOfIndexExceptions/KeyNotFoundException for nulls.
    • Rob
      Rob about 8 years
      The ?. operator simply wraps the code in if (val != null) { ...//Continue }. IndexOutOfRangeException can come from anywhere, and many classes do indeed throw it. An indexing operation also doesn't necessarily mean an index lookup in terms of traditional arrays, it's also used in dictionaries/lookups/etc. It wouldn't make sense for it to catch this kind of exception.
  • Scott Hannen
    Scott Hannen about 8 years
    Don't fearoeth the zeroeth.
  • Michael Sorens
    Michael Sorens about 8 years
    I actually did end up going with an extension method on Dictionary<TKey, TValue> called ElementAtOrNull that operates in a similar fashion by returning dictionary.ContainsKey(lookupValue) ? dictionary[lookupValue] : null
  • Derek
    Derek over 6 years
    It might be good to move the ElementAtOrDefault information closer to the top of the answer. It looks like it is a standard function, and does exactly what the user wants.
  • ephraim
    ephraim over 6 years
    you found a sollution to replace the [0] to "FirstOrDefault", but the question was more genral, regarding [index], where index might be out of arrays bounds..
  • justromagod
    justromagod almost 6 years
    override operator [] with swallowing ArgumentOutRange exception should work.
  • alas
    alas about 4 years
    This is the most elegant solution by far