Return null at any point in subproperty descent, if any proprety is null.



  • There have been times when I want a property to be null.
    At the same time, the property is an object that may have a property and so on.

    I may want to query a subproperty of a subproperty, knowing that either could be null. In this case I would want to return null if either is null, and I simply don't care which.

    So
    [code]
    var connection = myCar.DigitalRadio.BluetoothAux.Connection;
    if (connection == null) return;
    [/code]
    Maybe the car doesn't have a digital radio, maybe the digital radio doesn't have a bluetooth feature. Should I have to check at every depth whether the subproperty exists. Don't I only care if a digital radio bluetooth connection exists?

    Does this make sense to other people, or would this be a horrible idea that breaks some fundamental law of programming that I dare not cross or be a noob?

    I know implementing this in C# directly would be a nightmare of weird anti-patterns.

    But do I have a good case for wanting this?



  • That's a perfectly reasonable thing to want to do. One way to handle this might be an extension method:

    public static B Then<A, B>(this A obj, Func<A, B> f) where B : class
    {
        return obj == null ? null : f(obj);
    }
    

    Then you could write:

    var connection = myCar.Then(x => x.DigitalRadio)
                          .Then(x => x.BluetoothAux)
                          .Then(x => x.Connection);
    

    connection will end up being null at the end if any of myCar, DigitalRadio, BluetoothAux or Connection were null.

    You might also want to add the following in case you end up calling Then on a struct type:

    public static B Then<A, B>(this A obj, Func<A, B> f) where B : struct
    {
        return f(obj);
    }
    

    Filed under: I 💗 language abuse



  • @Bort said:

    var connection = myCar.Then(x => x.DigitalRadio)
    .Then(x => x.BluetoothAux)
    .Connection;

    Surely you could be trying to access the Connection property of a null object here.

    Edit: I can't figure out which magical incantation I need to show code within a quote.



  • You can do this in a hacky way by taking your thing you want to access as a string ("myCar.DigitalRadio.BluetoothAux.Connection") and then walk it using reflection and recursion to grab each part in turn. If you hit a part that is null then return null. It is messy but it works (using it in a slapped together internal framework).

    Note: your function sig would need to be something like
    object FindSubProp(object startObj, string lookingFor).



  • Equally, could you not simply wrap it in a try and catch a null pointer exception (or something)?

    All of these suggestions seem way too hacky. I feel like it would be better to have a custom class which you can return an instance of. If a property is requested of it, the object returns another instance of the same class. Comparison is overridden so that it compares equal with null.

    I feel dirty.



  • You mean like this?

    public struct Maybe<A>
    {
        internal Maybe(A val) : this()
        {
            Value = val;
            HasValue = val != null;
        }
    
        public A Value { get; private set; }
        public bool HasValue { get; private set; }
    
        public Maybe<B> Then<B>(Func<A, B> f)
        {
            return HasValue ? new Maybe<B>(f(Value)) : new Maybe<B>();
        }
    }
    
    public static class Maybe
    {
        public static Maybe<A> Of<A>(A x)
        {
            return new Maybe<A>(x);
        }
    
        public static Maybe<A> None<A>()
        {
            return new Maybe<A>();
        }
    }
    

    And then you make those properties Maybe<A> instead of just A.

    I think either of these solutions is better than putting null checks everywhere. If it feels hacky in a language like C# or Java, maybe that's not a problem with the design, but a problem with the language?



  • Why did I try to parse this code as Haskell?



  • @presidentsdaughter said:

    Why did I try to parse this code as Haskell?

    Haskell is prettier looking than C#.


  • BINNED

    @presidentsdaughter said:

    Why did I try to parse this code as Haskell?

    Because he implemented the Maybe type including its instance in the Functor typeclass (fmap)?



  • I love it.

    It's so much better than having a property on the top object like I ended up doing.

    Elegant.



  • Funny you should say that. The other day, I was thinking of what C# object references are like in Haskell terms:

    type Ref a = Pointer a | Null
    

    So everything most things that look like an a in C# (or Java or several other languages) is really a Ref a that could be Null. And if you try to do something with a Ref that happens to be Null, well, you know the result.

    They gave the language a type system and then immediately made things tricky by overloading almost every type with an additional boobytrap value.

    In C#, at least, you can sort of mitigate this problem:

    public struct Sure<A>
    {
        public Sure(A val)
        {
            if (val == null) throw new NullReferenceException();
            Value = val;
        }
    
        public A Value { get; private set; }
    
        public static implicit operator A(Sure<A> sure)
        {
            return sure.Value;
        }
    
        public static implicit operator Sure<A>(A val)
        {
            return new Sure<A>(val);
        }
    }
    
    public static class Sure
    {
        public static Sure<A> Of<A>(A val)
        {
            return new Sure<A>(val);
        }
    }
    

    Because references to structs can't be null. So if a method takes a Sure<String>, inside the method, you can be sure that the Sure<String> has a non-null value.

    But then, I realized - every struct type has an explicit parameter-less constructor! And it initializes all properties to their defaults! So new Sure<String>() would return a Sure that has null for its Value property!

    But, hold on, maybe we can do this:

    public struct Sure<A>
    {
    ...
        private Sure() { throw new Exception(); }
    ...
    }
    

    And we can be sure again.

    No, wait - "structs cannot contain explicit parameters constructors". So the whole Sure thing is a waste of time, there's no way to consistently avoid having possible null references on every fucking line of code and we're back to square fucking one! It's almost like they made a deliberate effort on this one!

    Dammit!



  • I'm not a C# person, but I was under the impression this would work...

    ```c# var connection = myCar?: mycar.DigitalRadio?: mycar.DigitalRadio.BluetoothAux?: mycar.DigitalRadio.BluetoothAux.Connection; ```




  • Tried this with struct, and it just didn't make a lot of sense.
    Either you will end up having a defaulted struct returned, which is a lie (against my assumptions that null tells me the chain broke), or you'll have a value under the struct returned, and same lie.

    It seems if you have a struct under an object with safe navigation, you'll need to break and check the previous object for null before navigating into the struct, if you are relying on the null telling you anything.

    If you don't care, then it's fine if you get a defaulted struct back.

    But then you don't need a [code]Then<A,B> when struct[/code]

    Also, for some reason, even though I specified different where clauses, it still saw the two methods as ambiguous, so you have to name them differently for this to work. And then you have to be aware that you are about to enter a struct.

    In the end, I think it's just better to end your chain prematurely before a struct, and check for null then.

    If you have a class -> struct -> class -> struct scenario, then you are probably doing things wrong.



  • Ah, I didn't realize that might happen. Maybe it should have been written with both A and B constrained to a struct:

    public static B Then<A, B>(this A obj, Func<A, B> f) where A : struct, B : struct
    {
        return f(obj);
    }
    

    'cause it can't return a null if B is struct, so it can't take a null: A can't be struct. And if A weren't struct, there'd be no reason to return null.

    But I don't feel like experimenting right now.

    @xaade said:

    If you have a class -> struct -> class -> struct scenario, then you are probably doing things wrong.

    Agreed.


Log in to reply