344

I have a generic method defined like this:

public void MyMethod<T>(T myArgument)

The first thing I want to do is check if the value of myArgument is the default value for that type, something like this:

if (myArgument == default(T))

But this doesn't compile because I haven't guaranteed that T will implement the == operator. So I switched the code to this:

if (myArgument.Equals(default(T)))

Now this compiles, but will fail if myArgument is null, which is part of what I'm testing for. I can add an explicit null check like this:

if (myArgument == null || myArgument.Equals(default(T)))

Now this feels redundant to me. ReSharper is even suggesting that I change the myArgument == null part into myArgument == default(T) which is where I started. Is there a better way to solve this problem?

I need to support both references types and value types.

Tim B
  • 40,716
  • 16
  • 83
  • 128
Stefan Moser
  • 6,663
  • 9
  • 35
  • 48
  • 1
    C# now supports [Null Conditional Operators](https://msdn.microsoft.com/en-us/library/dn986595.aspx), which is syntatic sugar for the last example you give. Your code would become `if (myArgument?.Equals( default(T) ) != null )`. – wizard07KSU Mar 09 '17 at 02:45
  • 1
    @wizard07KSU That doesn't work for value types, i.e. evaluates to `true` in any case because `Equals` wil always be called for value types since `myArgument` cannot be `null` in this case and the result of `Equals` (a boolean) will never be `null`. – jasper Jan 31 '18 at 22:30
  • Equally valuable almost-duplicate (so not voting to close): [Can't operator == be applied to generic types in C#?](https://stackoverflow.com/q/390900/11683) – GSerg Jun 14 '19 at 17:53

14 Answers14

722

To avoid boxing, the best way to compare generics for equality is with EqualityComparer<T>.Default. This respects IEquatable<T> (without boxing) as well as object.Equals, and handles all the Nullable<T> "lifted" nuances. Hence:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

This will match:

  • null for classes
  • null (empty) for Nullable<T>
  • zero/false/etc for other structs
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 44
    Wow, how delightfully obscure! This is definitely the way to go though, kudos. – Nick Farina May 14 '09 at 20:03
  • 1
    Definitely the best answer. No squiggly lines in my code after rewriting to use this solution. – Nathan Ridley Jun 17 '09 at 07:20
  • 19
    Great answer! Even better is adding an extension method for this line of code so that you can go obj.IsDefaultForType() – rikoe Sep 06 '10 at 15:56
  • 1
    Marc, can you tell me how this is the best way? I mean doesnt `T.Equals` too respect `IEquatable` interface? lets say `Person` class/struct is `IEquatable`. Now wouldn't calling `p1.Equals(p2)` call the generic version first and if not present then `object.Equals`? Isn't that what `EqualityComparer.Default.Equals` too does? – nawfal Feb 04 '13 at 10:05
  • 3
    @nawfal in the case of `Person`, `p1.Equals(p2)` would depend on whether it implements `IEquatable` on the public API, or via explicit implementation - i.e. can the compiler see a public `Equals(Person other)` method. However; in *generics*, the same IL is used for all `T`; a `T1` that happens to implement `IEquatable` needs to be treated identically to a `T2` that does not - so no, it **will not** spot a `Equals(T1 other)` method, even if it exists at runtime. In both cases, there is also `null` to think about (either object). So with generics, I would use the code I posted. – Marc Gravell Feb 04 '13 at 10:09
  • @MarcGravell o yes. Its hard to spot these minor differences when framework gives us 100 ways to do a thing. And mostly the culprit? Non generic .NET 1.1! ;) – nawfal Feb 04 '13 at 10:15
  • 14
    I can't decide if this answer pushed me away from or closer to insanity. +1 – Steven Liekens Apr 18 '17 at 15:54
  • You could also just use the static Object.Equals method. – Jordan Nov 07 '17 at 14:47
  • 2
    @Jordan first three words: "to avoid boxing". `Object.Equals` will cause boxing – Marc Gravell Nov 07 '17 at 14:55
  • I'm not sure why boxing is a problem here. https://dotnetfiddle.net/ctlPpj – Jordan Nov 07 '17 at 17:23
  • 2
    @Jordan [here's the IL for that](https://gist.github.com/mgravell/7c4134374b73610238b1ea0d300e700b) - as you can see, it clearly boxes. Now, whether boxing is an *issue for you* is contextual. It clearly won't matter for your example since the program exits straight away. However, every box is an allocation. If you deal with anything non-trivial, avoiding *unnecessary* allocations is always to be recommended. These allocations are unnecessary. – Marc Gravell Nov 07 '17 at 17:28
  • @MarcGravell, only value type and structs are boxed, right? Any time you pass a value type or struct, it allocates a new value type or struct. You aren't using references after all. I can't fathom a way in which comparing two objects of the same type would be harmed by boxing. Doesn't mean there isn't one. – Jordan Nov 07 '17 at 17:32
  • 5
    @Jordan that isn't what boxing means; what you just described is "pass by value", and yes: that happens for structs. But boxing is when you treat a value-type as `object` (or an interface, such as `IEquatable`); this includes passing value-types as parameters of type `object`, which is what `object.Equals` has. When you do this, the value is "boxed" into an object, which means: a new managed-heap object is allocated which encapsulates a copy of the value. When you box two integers: nobody will care. But when this is in a hot-path loop, you can allocate **lots**, **very very quickly** - bad. – Marc Gravell Nov 07 '17 at 17:36
  • @Jordan the "harm" is simply: by calling `object.Equals(valueX, valueY)`, you have allocated 2 new objects on the managed heap; multiply that by how often you call the method, and you *could* have a very real GC problem – Marc Gravell Nov 07 '17 at 17:38
  • @MarcGravell, that's fair. I do know what boxing is. – Jordan Nov 07 '17 at 17:41
  • As a rather related (to the OP's question) side note, I do wonder why `value == default` and `value == default(T)` [behave differently](https://stackoverflow.com/q/56602309/11683) in C# 7.1+ in the context of generics. – GSerg Jun 14 '19 at 18:51
  • 2
    @MarcGravell I have used for several years now (thanks BTW), I recently enabled Nullable reference types in my project and know I'm getting a warning that default(T) may be null (CS8604), do you know any other way to check for null, avoid boxing and using generics?. – Juan Zamudio Jul 07 '20 at 01:59
  • 1
    @Juan `default(T)!` should tell it to not complain – Marc Gravell Jul 07 '20 at 06:50
  • @MarcGravell thanks that worked, I was reading this https://github.com/dotnet/runtime/issues/30998 and was under the impression that the method will throw if passed null. – Juan Zamudio Jul 07 '20 at 14:16
  • @JuanZamudio, that discussion (30998) seems to say that the implementation doesn't actually throw on null even though it is documented that it could throw on null. – sjb-sjb Jul 24 '20 at 02:50
  • 1
    @rikoe I agree! Why didn't they add something like `public static bool IsDefault(this T value) => EqualityComparer.Default.Equals(value, default(T)); ` to the framework? – mike Feb 06 '21 at 00:10
  • 1
    Late to this discussion, but as of .NET 6.0, it seems like it is possible to drop the (T), and just write this as EqualityComparer.Default.Equals(value, default) - or at least, my compiler seems to let me. – Kiran Ramaswamy Dec 04 '21 at 16:00
123

How about this:

if (object.Equals(myArgument, default(T)))
{
    //...
}

Using the static object.Equals() method avoids the need for you to do the null check yourself. Explicitly qualifying the call with object. probably isn't necessary depending on your context, but I normally prefix static calls with the type name just to make the code more soluble.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 3
    You can even drop the "object." part since it's redundant. if (Equals(myArgument, default(T))) – Stefan Moser Sep 15 '08 at 18:41
  • 13
    True, it normally is, but may not be depending on the context. There may be an instance Equals() method that takes two arguments. I tend to explicitly prefix all static calls with the class name, if only to make the code easier to read. – Kent Boogaart Sep 15 '08 at 18:41
  • 8
    Need to note that it will cause boxing and in some cases it may be important – nightcoder Aug 04 '10 at 12:34
  • 2
    For me this doesn't work when using integers that are already boxed. Because it will then be an object and the default for object is null instead of 0. – riezebosch Nov 07 '11 at 19:11
33

I was able to locate a Microsoft Connect article that discusses this issue in some detail:

Unfortunately, this behavior is by design and there is not an easy solution to enable use of with type parameters that may contain value types.

If the types are known to be reference types, the default overload of defined on object tests variables for reference equality, although a type may specify its own custom overload. The compiler determines which overload to use based on the static type of the variable (the determination is not polymorphic). Therefore, if you change your example to constrain the generic type parameter T to a non-sealed reference type (such as Exception), the compiler can determine the specific overload to use and the following code would compile:

public class Test<T> where T : Exception

If the types are known to be value types, performs specific value equality tests based on the exact types used. There is no good "default" comparison here since reference comparisons are not meaningful on value types and the compiler cannot know which specific value comparison to emit. The compiler could emit a call to ValueType.Equals(Object) but this method uses reflection and is quite inefficient compared to the specific value comparisons. Therefore, even if you were to specify a value-type constraint on T, there is nothing reasonable for the compiler to generate here:

public class Test<T> where T : struct

In the case you presented, where the compiler does not even know whether T is a value or reference type, there is similarly nothing to generate that would be valid for all possible types. A reference comparison would not be valid for value types and some sort of value comparison would be unexpected for reference types that do not overload.

Here is what you can do...

I have validated that both of these methods work for a generic comparison of reference and value types:

object.Equals(param, default(T))

or

EqualityComparer<T>.Default.Equals(param, default(T))

To do comparisons with the "==" operator you will need to use one of these methods:

If all cases of T derive from a known base class you can let the compiler know using generic type restrictions.

public void MyMethod<T>(T myArgument) where T : MyBase

The compiler then recognizes how to perform operations on MyBase and will not throw the "Operator '==' cannot be applied to operands of type 'T' and 'T'" error that you are seeing now.

Another option would be to restrict T to any type that implements IComparable.

public void MyMethod<T>(T myArgument) where T : IComparable

And then use the CompareTo method defined by the IComparable interface.

Community
  • 1
  • 1
Eric Schoonover
  • 47,184
  • 49
  • 157
  • 202
  • 5
    "this behavior is by design and there is not an easy solution to enable use of with type parameters that may contain value types." Actually Microsoft is wrong. There is an easy solution: MS should extend the ceq opcode to function on value types as a bitwise operator. Then they could provide an intrinsic that simply uses this opcode, e.g. object.BitwiseOrReferenceEquals(value, default(T)) that simply uses ceq. For both value and reference types this would check for bitwise equality *of the value* (but for reference types, reference bitwise equality is the same as object.ReferenceEquals) – Qwertie Jul 07 '11 at 16:38
  • 1
    I think the Microsoft Connect link you wanted was http://connect.microsoft.com/VisualStudio/feedback/details/304501/operator-cannot-be-applied-to-operands-of-type-t-and-t – Qwertie Jul 07 '11 at 17:00
22

Try this:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

that should compile, and do what you want.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Isn't the default(T) redundant? EqualityComparer.Default.Equals(myArgument) should do the trick. – Joshcodes Jun 24 '13 at 16:00
  • 2
    1) did you try it, and 2) what are you then comparing against, the comparer object? The `Equals` method of `IEqualityComparer` takes two arguments, the two objects to compare, so no, it is not redundant. – Lasse V. Karlsen Jun 24 '13 at 16:03
  • This is even better than the accepted answer IMHO because it handles boxing/unboxing and other types. See this "closed as dupe" questions' answer: http://stackoverflow.com/a/864860/210780 – ashes999 Aug 14 '13 at 16:30
9

(Edited)

Marc Gravell has the best answer, but I wanted to post a simple code snippet I worked up to demonstrate it. Run this in a simple C# console app:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True
        
    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(""));     //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null));   //True 

    Console.ReadKey();
}

For fun, you can also do it as an extension method:

public static class TypeHelper
{
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

static void Main()
{
    // value type
    Console.WriteLine(1.IsDefault());
    Console.WriteLine(0.IsDefault());

    // reference type
    Console.WriteLine("test".IsDefault());
    // null must be cast to a type
    Console.WriteLine(((String)null).IsDefault());
    // Or called with an explicit type argument:
    Console.WriteLine(null.IsDefault<object>());
}
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 4
    It does "work" as an extension method. Which is interesting since it works even if you say o.IsDefault() when o is null. Scary =) – Nick Farina May 14 '09 at 17:32
8

To handle all types of T, including where T is a primitive type, you'll need to compile in both methods of comparison:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }
George Stocker
  • 57,289
  • 29
  • 176
  • 237
Nick Farina
  • 4,470
  • 2
  • 32
  • 30
  • 1
    Note that the function has been changed to accept Func and return T, which I think was accidentally omitted from the questioner's code. – Nick Farina May 14 '09 at 16:18
  • Seems ReSharper is messing with me. Didn't realise its warning about a possible comparison between a value type and null was not a compiler warning. – Nathan Ridley May 14 '09 at 16:19
  • 4
    FYI: If T turns out to be a value type then the comparison against null will be treated as always false by the jitter. – Eric Lippert May 14 '09 at 16:48
  • Makes sense - the runtime will be comparing a pointer to a value type. The Equals() check does work in that case however (interestingly, since it seems very dynamic-language to say 5.Equals(4) which does compile). – Nick Farina May 14 '09 at 17:27
  • 3
    See the EqualityComparer answer for an alternative that doesn't involve boxing et – Marc Gravell May 14 '09 at 18:58
5

Extension method based on accepted answer.

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Usage:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

Alternate with null to simplify:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Usage:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }
dynamiclynk
  • 2,275
  • 27
  • 31
  • Ah yes, that weird little detail that you can perfectly call an extension method on a null object without getting a null pointer exception. Honestly, the syntax of calling `obj.ExecuteSomething` on an `obj` that may be null looks really scary. – Nyerguds Feb 26 '21 at 08:07
  • Just a small note: no need for null check, Equals does that – mBardos Jul 18 '23 at 08:23
4

There is going to be a problem here -

If you're going to allow this to work for any type, default(T) will always be null for reference types, and 0 (or struct full of 0) for value types.

This is probably not the behavior you're after, though. If you want this to work in a generic way, you probably need to use reflection to check the type of T, and handle value types different than reference types.

Alternatively, you could put an interface constraint on this, and the interface could provide a way to check against the default of the class/struct.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
0

I use:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}
kofifus
  • 17,260
  • 17
  • 99
  • 173
0

Just a hacky answer and as a reminder for myself. But I find this quite helpful for my project. The reason I write it like this is that because I don't want default integer 0 being marked as null if the value is 0

private static int o;
public static void Main()
{
    //output: IsNull = False -> IsDefault = True
    Console.WriteLine( "IsNull = " + IsNull( o ) + " -> IsDefault = " + IsDefault(o)); 
}

public static bool IsNull<T>(T paramValue)
{
  if( string.IsNullOrEmpty(paramValue + "" ))
    return true;
  return false;
}

public static bool IsDefault<T>(T val)
{
  return EqualityComparer<T>.Default.Equals(val, default(T));
}
Kosmas
  • 353
  • 4
  • 11
0

I think you probably need to split this logic into two parts and check for null first.

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

In the IsNull method, we're relying on the fact that ValueType objects can't be null by definition so if value happens to be a class which derives from ValueType, we already know it's not null. On the other hand, if it's not a value type then we can just compare value cast to an object against null. We could avoid the check against ValueType by going straight to a cast to object, but that would mean that a value type would get boxed which is something we probably want to avoid since it implies that a new object is created on the heap.

In the IsNullOrEmpty method, we're checking for the special case of a string. For all other types, we're comparing the value (which already know is not null) against it's default value which for all reference types is null and for value types is usually some form of zero (if they're integral).

Using these methods, the following code behaves as you might expect:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}
Damian Powell
  • 8,655
  • 7
  • 48
  • 58
-1

Don't know if this works with your requirements or not, but you could constrain T to be a Type that implements an interface such as IComparable and then use the ComparesTo() method from that interface (which IIRC supports/handles nulls) like this:

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

There are probably other interfaces that you could use as well IEquitable, etc.

caryden
  • 7,738
  • 3
  • 30
  • 31
-2

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

Operator '==' cannot be applied to operands of type 'T' and 'T'

I can't think of a way to do this without the explicit null test followed by invoking the Equals method or object.Equals as suggested above.

You can devise a solution using System.Comparison but really that's going to end up with way more lines of code and increase complexity substantially.

cfeduke
  • 23,100
  • 10
  • 61
  • 65
-3

I think you were close.

if (myArgument.Equals(default(T)))

Now this compiles, but will fail if myArgument is null, which is part of what I'm testing for. I can add an explicit null check like this:

You just need to reverse the object on which the equals is being called for an elegant null-safe approach.

default(T).Equals(myArgument);
nawfal
  • 70,104
  • 56
  • 326
  • 368
Scott McKay
  • 190
  • 1
  • 8