简体   繁体   中英

setIfNull Extention Method will not set the value to the object

I want to create a generic extension method that will set a value to an Object or a Struct if their value equals to their default value.

so i have the following code:

public static void setIfNull<T>(this T i_ObjectToUpdate, T i_DefaultValue)
{
    if (EqualityComparer<T>.Default.Equals(i_ObjectToUpdate, default(T)))
    {
        i_ObjectToUpdate = i_DefaultValue;
    }
}

and here is a call example:

public OrganizationalUnit CreateOrganizationalUnit(OrganizationalUnit i_UnitToCreate)
{
    i_UnitToCreate.EntityCreationDate.setIfNull(DateTime.Now); //Here is a call
    i_UnitToCreate.EntityLastUpdateDate.setIfNull(DateTime.Now); //And another one
    m_Context.DomainEntities.Add(i_UnitToCreate);
    return i_UnitToCreate;
}

I don't know if it have anything to do with it but i use entity framework and MVC.

What actually happens in a debugger I see that the line in the extension method i_ObjectToUpdate = i_DefaultValue; is working and the values are changes but when the debugger gets out of the extension method I see that the value of i_UnitToCreate.EntityCreationDate remains unchaged.

Any ideas what went wrong ?

You have two references in your code. One is i_UnitToCreate.EntityCreationDate which points to some address in memory. And other is i_ObjectToUpdate in your extension method, which initially also points to that address (you are creating copy of address when passing i_UnitToCreate.EntityCreationDate reference to your method). Later you change second reference to point on other object in memory, but that does not change first reference, because they are independent.

Workaround

public static void SetIfDefault<T, TProperty>(this T arg,
    Expression<Func<T, TProperty>> propertySelector, TProperty value)
{
    TProperty currentValue = propertySelector.Compile()(arg);
    EqualityComparer<TProperty> comparer = EqualityComparer<TProperty>.Default;
    if (!comparer.Equals(currentValue, default(TProperty)))
        return;
    PropertyInfo property = (PropertyInfo)((MemberExpression)propertySelector.Body).Member;
    property.SetValue(arg, value);
} 

You can use expression to pass property selector (not property value) to extension method. Retrieve value from compiled expression. If it is default value, then with reflection (you can easily get PropertyInfo by casting member expression) set new value. Usage:

i_UnitToCreate.SetIfDefault(x => x.EntityCreationDate, DateTime.Now);
i_UnitToCreate.SetIfDefault(x => x.EntityLastUpdateDate, DateTime.Now);

PS There is one remark about my first answer - I thought about generic T type as a reference type, when talked about copying reference values. With value types (as DateTime) whole object is copied. It does not change result (you can't assign new value), but needs to be mentioned.

The issue is, within your extension method, i_ObjectToUpdate is a local reference (pointer) to some object sitting in the heap. When you set:

i_ObjectToUpdate = i_DefaultValue

you are not actually changing the object in the heap , you are making your local reference i_ObjectToUpdate point to the object that i_DefaultValue points to , but only within the scope of your extension method. The original references in the calling context:

i_UnitToCreate.EntityCreationDate
i_UnitToCreate.EntityLastUpdateDate

remain unchanged.

To get around this issue, you have to actually pass pointers to your pointers to the extension method. This is done by adding a ref keyword:

    public static void setIfNull<T>(this ref T i_ObjectToUpdate, T i_DefaultValue)
    {
        if (EqualityComparer<T>.Default.Equals(i_ObjectToUpdate, default(T)))
        {
            i_ObjectToUpdate = i_DefaultValue;
        }
    }

Unfortunately, extension methods do not support the ref keyword. Your alternative is to use the method as a standard static method:

    public static void setIfNull<T>(ref T i_ObjectToUpdate, T i_DefaultValue)
    {
        if (EqualityComparer<T>.Default.Equals(i_ObjectToUpdate, default(T)))
        {
            i_ObjectToUpdate = i_DefaultValue;
        }
    }

and calling it like so:

Program.setIfNull(i_UnitToCreate.EntityCreationDate, DateTime.Now);

Ideally, you could create a different extension method that only does the null checking, then set the variable yourself:

    public static T IfNull<T>(this T i_ObjectToUpdate, T i_DefaultValue)
    {
        if (EqualityComparer<T>.Default.Equals(i_ObjectToUpdate, default(T)))
        {
            return i_DefaultValue;
        }
        return i_ObjectToUpdate;
    }

And calling it via:

i_UnitToCreate.EntityLastUpdateDate = _UnitToCreate.EntityLastUpdateDate.IfNull(DateTime.Now);

which is equivalent to (when using reference types):

i_UnitToCreate.EntityLastUpdateDate = i_UnitToCreate.EntityLastUpdateDate ?? DateTime.Now;

I believe a better approach is to use the C# 4.0 ?? operator:

   var nullable;

   . . . 

   nullable = nullable ?? "defaultValue";

What are you trying to achieve? In my own personal working habits, I got very frustrated that C# IDictionary objects throw an exception when a referenced key is not present ( dict['not here'] throws an exception) that I wrote an extension method:

public static TValue Safeget<TValue, TKey>(this IDictionary<TKey, TValue> arg, TKey key, TValue defaultValue = default(TValue))
        {
            if (arg.ContainsKey(key))
            {
                return arg[key];
            }

            return defaultValue;
        }

Is this the kind of thing you are after?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM