Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to emulate C# 6 null-conditional in C# < 6

Tags:

c#

c#-5.0

c#-6.0

With C# 6.0 I can do this

var isEqual = x.Id == y.Id
              && x.UpdatedAt == y.UpdatedAt
              && x.Name == y.Name                        
              && x.RulesUrl == y.RulesUrl
              && x.OngoingChallenges?.Count == y.OngoingChallenges?.Count
              && x.MembershipIds?.Count == y.MembershipIds?.Count;

Is there any nice solution to do this with C# < 6.0?

I mean this part

&& x.OngoingChallenges?.Count == y.OngoingChallenges?.Count
&& x.MembershipIds?.Count == y.MembershipIds?.Count;

Because in old projects we do not have possibility to use C# 6.0. How to write isEqual efficiently?

like image 811
senzacionale Avatar asked Dec 27 '15 19:12

senzacionale


3 Answers

x.OnGoingChallenges?.Count is equivalent to x.OnGoingChallenges != null ? x.OnGoingChallenges.Count : default(int?) (there're other approaches, but at the end of the day is a shortcut to null checking called null-conditional operator).

That is, your code can't be rewritten with a syntatically elegant statement without C# 6, but you can emulate this new C# 6 feature using extension methods...

public static class StructExtensions
{
    // Check that TProperty is nullable for the return value (this is how C#6's
    // null-conditional operator works with value types
    public static TProperty? GetOrDefault<TObject, TProperty>(this TObject someObject, Func<TObject, TProperty> propertySelectionFunc)
        where TObject : class 
        where TProperty : struct
    {
        Contract.Requires(propertySelectionFunc != null);

        return someObject == null ? default(TProperty?) : propertySelectionFunc(someObject);
    }
}

And now your code in C#5 would look as follows:

var isEqual = x.Id == y.Id
                          && x.UpdatedAt == y.UpdatedAt
                          && x.Name == y.Name                        
                          && x.RulesUrl == y.RulesUrl
                          && x.OngoingChallenges.GetOrDefault(c => c.Count) == y.OngoingChallenges.GetOrDefault(c => c.Count)
                          && x.MembershipIds.GetOrDefault(m => m.Count) == x.MembershipIds.GetOrDefault(m => m.Count);

The whole extension method would work for getting a value-typed property value or its default value. You might or might not extend the extension method class to also support getting a reference type value or null.

like image 183
Matías Fidemraizer Avatar answered Sep 17 '22 14:09

Matías Fidemraizer


In C# version < 6.0 you would use ternary expressions

var isEqual = x.Id == y.Id
    && x.UpdatedAt == y.UpdatedAt
    && x.Name == y.Name                        
    && x.RulesUrl == y.RulesUrl
    && (x.OngoingChallenges == null ? 0 : x.OngoingChallenges.Count) ==
       (y.OngoingChallenges == null ? 0 : y.OngoingChallenges.Count)
    && (x.MembershipIds == null : 0 ? x.MembershipIds.Count) == 
       (y.MembershipIds == null : 0 : y.MembershipIds.Count);

As @Hamlet Hakobyan has pointed out, this not the semantically exact equivalent of the original C# 6.0 solution using ?., but you could change it to (according to @hvd):

int? count = x.MembershipIds == null : default(int?) ? x.MembershipIds.Count;

It depends whether you want to consider a missing collection and an empty collection as equal or not.


You could also use the null-coalescing operator ?? and provide a replacement object. Assuming that your objects are lists of some kind:

var empty = new List<int>();
var isEqual = x.Id == y.Id
    && x.UpdatedAt == y.UpdatedAt
    && x.Name == y.Name                        
    && x.RulesUrl == y.RulesUrl
    && (x.OngoingChallenges ?? empty).Count == (y.OngoingChallenges ?? empty).Count
    && (x.MembershipIds ?? empty).Count == (y.MembershipIds ?? empty).Count;
like image 37
Olivier Jacot-Descombes Avatar answered Sep 19 '22 14:09

Olivier Jacot-Descombes


Before C# 6, i used something like this

 public static class CommonExtensions
 {
     public static TValue TryGet<TObject, TValue>(this TObject obj, Func<TObject, TValue> getter, TValue defaultValue = default(TValue))
         where TObject : class
     {
         return obj == null ? defaultValue : getter(obj);
     }

     //If objects types are equals
     public static bool KeyEquals<TObject, TValue>(this TObject a, TObject b, Func<TObject, TValue> keyGetter)
        where TObject : class
     {
         return a != null 
             && b != null 
             && EqualityComparer<TValue>.Default.Equals(keyGetter(a), keyGetter(b));
     }
 }



 var isEqual = x.Id == y.Id
               && x.UpdatedAt == y.UpdatedAt
               && x.Name == y.Name                        
               && x.RulesUrl == y.RulesUrl
               //v1
               && x.OngoingChallenges.TryGet(v => v.Count) == y.OngoingChallenges.TryGet(v => v.Count)
               //v2
               && x.MembershipIds.KeyEquals(y.MembershipIds, v => v.Count);
like image 23
Kozylov Mikhail Avatar answered Sep 18 '22 14:09

Kozylov Mikhail