1

I want to test whether a variable of unknown type has been assiged a non-default value.

The variable is probably a struct type, so I can't solve this with where T : class.

The struct's IEquatable<T> implementation will usually assume that its fields have been assigned to already, so I can't use EqualityComparer<T>.default, or else it will crash with a null pointer exception.

(Yes, I'm being careful to ensure that the 0 value for a struct is not ever treated as a valid value, so I'm sure I can treat it specially.)

I'm willing to turn on /unsafe to accomplish this. I would like to be able to write the body to this function:

unsafe static bool UnsafeIsDefault<T>(T a) {
    // Error: Operator '==' cannot be applied to operands of type 'T' and 'T'
    // return a == default(T);

    // Real body goes here
}

I realize that another solution would be to constrain where T : ICheckForDefault with interface ICheckForDefault { bool IsDefault(); }, and write an implementation of that for every type I intend to use here, but I was hoping to avoid that.

  • 1
    Anything wrong with `object.Equals(value, default(T))`? – stelioslogothetis Jun 17 '17 at 15:57
  • Using `IEquatable` you can do `myStruct.Equals(default(T));`where T is type of your struct – M.kazem Akhgary Jun 17 '17 at 16:07
  • `default(T)` is not necessarily a safe value of the struct, so calling its `Equals` implementation will crash. I really just want a bitwise comparison here, and a logical comparison everywhere else. –  Jun 17 '17 at 16:13
  • You can use object.Equals(value, default(T)) as suggested above. It will not invoke your struct Equals method, even if you overrided that or implemented IEquatable. So even if you have some struct which does not expect default field values in its equality comparisions - it will not crash. – Evk Jun 17 '17 at 16:56
  • 1
    @Evk I tested it out and `object.Equals` does seem to be calling an overridden `Equals`. The [documentation](https://msdn.microsoft.com/en-us/library/w4hkze5k(v=vs.110).aspx) also says "This means that if objA overrides the Object.Equals(Object) method, this override is called." @PetSerAI That works! I'd accept that as an answer if you submit it. But does it box structs? –  Jun 17 '17 at 17:09
  • The [documentation](https://msdn.microsoft.com/en-us/library/edy4ke1e.aspx) for RuntimeHelpers.Equals says that it calls `o1.Equals(o2)`, but my tests don't show that happening. –  Jun 17 '17 at 17:15
  • Yes it calls `bool Equals(object)` overload indeed, missed that in my test. Well after a couple of checks `object.Equals` ends up calling `RuntimeHelpers.Equals` mentioned above so you indeed can use that right away. – Evk Jun 17 '17 at 17:17
  • _"I can't use EqualityComparer.default, or else it will crash with a null pointer exception"_ -- I don't understand that statement at all. If you have a `struct` that implements `IEquatable`, but it can't handle a default value of `T`, that `struct` is broken. And your focus on `/unsafe` is particularly confusing, as there doesn't appear to be anything in your question at all that actually involves `/unsafe`, except your apparent willingness to use that. Please provide a good [mcve] that reliably reproduces whatever problem it is you're trying to avoid, so we can help you avoid it. – Peter Duniho Jun 17 '17 at 17:59

2 Answers2

0

This one assumes that a variable (class or struct) which is default(T) or just initialized and no field/property changed is default.

 class Program
    {
        static void Main(string[] args)
        {
            var defaultStruct = default(MyStruct);
            PrintIsDefault(IsDefault(defaultStruct), nameof(defaultStruct));

            var defaultInitializedStruct = new MyStruct();
            PrintIsDefault(IsDefault(defaultInitializedStruct), nameof(defaultInitializedStruct));

            var nonDefaultStruct = new MyStruct { Field1 = 13 };
            PrintIsDefault(IsDefault(nonDefaultStruct), nameof(nonDefaultStruct));

            var defaultChar = default(char);
            PrintIsDefault(IsDefault(defaultChar), nameof(defaultChar));

            var nonDefaultChar = 'a';
            PrintIsDefault(IsDefault(nonDefaultChar), nameof(nonDefaultChar));

            var defaultObject = default(object);
            PrintIsDefault(IsDefault(defaultObject), nameof(defaultObject));

            var nonDefaultObject = "string";
            PrintIsDefault(IsDefault(nonDefaultObject), nameof(nonDefaultObject));

            var defaultClass = default(MyClass);
            PrintIsDefault(IsDefault(defaultClass), nameof(defaultClass));

            var defaultInitializedClass = default(MyClass);
            PrintIsDefault(IsDefault(defaultInitializedClass), nameof(defaultInitializedClass));

            var nonDefaultClass = new MyClass { Field1 = 1, Prop1 = 2 };
            PrintIsDefault(IsDefault(nonDefaultClass), nameof(nonDefaultClass));

            Console.ReadLine();
        }

        private static bool IsDefault<T>(T value)
        {
            var typeInfo = typeof(T).GetTypeInfo();

            if (typeInfo.IsClass)
            {
                if (typeInfo.IsPrimitive || value is string || value is object)
                {
                    return Equals(value, default(T));
                }
                else
                {
                    return Equals(value, default(T)) ? true : AreMembersDefault(value);
                }
            }
            else
            {
                return typeInfo.IsPrimitive ? Equals(value, default(T)) : AreMembersDefault(value);
            }
        }

        private static bool AreMembersDefault<T>(T value)
        {
            var fields = value.GetType().GetFields();
            foreach (var field in fields)
            {
                if (!IsDefault((dynamic)(field.GetValue(value))))
                {
                    return false;
                }
            }

            var properties = value.GetType().GetProperties();
            foreach (var prop in properties)
            {
                if (!IsDefault((dynamic)(prop.GetValue(value))))
                {
                    return false;
                }
            }

            return true;
        }

        private static void PrintIsDefault(bool isDefault, string varName)
        {
            Console.WriteLine($"{varName} is default: {isDefault}");
        }
    }

Outputs:

defaultStruct is default: True
defaultInitializedStruct is default: True
nonDefaultStruct is default: False
defaultChar is default: True
nonDefaultChar is default: False
defaultObject is default: True
nonDefaultObject is default: False
defaultClass is default: True
defaultInitializedClass is default: True
nonDefaultClass is default: False
Mert Akcakaya
  • 3,109
  • 2
  • 31
  • 42
0

There's a different type of comparison that would be more suitable, called Equals.

Try the following code:

System.Runtime.CompilerServices.RuntimeHelpers.Equals(value, default(T))
WonderWorker
  • 8,539
  • 4
  • 63
  • 74