【问题标题】:setIfNull Extention Method will not set the value to the objectsetIfNull 扩展方法不会给对象设置值
【发布时间】:2012-11-24 09:38:51
【问题描述】:

我想创建一个通用扩展方法,如果它们的值等于它们的默认值,它将为 Object 或 Struct 设置一个值。

所以我有以下代码:

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;
    }
}

这是一个调用示例:

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;
}

我不知道它是否与它有关,但我使用实体框架和 MVC。

在调试器中实际发生的情况我看到扩展方法 i_ObjectToUpdate = i_DefaultValue; 中的行正在工作,并且值发生了变化,但是当调试器退出扩展方法时,我看到 i_UnitToCreate.EntityCreationDate 的值保持不变。

任何想法出了什么问题?

【问题讨论】:

标签: c# entity-framework generics reflection extension-methods


【解决方案1】:

您的代码中有两个引用。一个是i_UnitToCreate.EntityCreationDate,它指向内存中的某个地址。另一个是您的扩展方法中的i_ObjectToUpdate,它最初也指向该地址(在将i_UnitToCreate.EntityCreationDate 引用传递给您的方法时,您正在创建地址副本)。稍后您将第二个引用更改为指向内存中其他对象的点,但这不会更改第一个引用,因为它们是独立的。

解决方法

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);
} 

您可以使用表达式将属性选择器(不是属性值)传递给扩展方法。从编译的表达式中检索值。如果是默认值,则通过反射(您可以通过转换成员表达式轻松获取PropertyInfo)设置新值。用法:

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

PS 关于我的第一个答案有一个评论 - 在谈到复制引用值时,我认为泛型 T 类型作为引用类型。使用值类型(如 DateTime)复制整个对象。它不会改变结果(您不能分配新值),但需要提及。

【讨论】:

    【解决方案2】:

    问题是,在您的扩展方法中,i_ObjectToUpdate 是对位于堆中的某个对象的本地引用(指针)。设置时:

    i_ObjectToUpdate = i_DefaultValue
    

    实际上并未更改堆中的对象,您正在使 本地引用 i_ObjectToUpdate 指向 i_DefaultValue 指向的对象,但仅在您的扩展方法范围内。调用上下文中的原始引用:

    i_UnitToCreate.EntityCreationDate
    i_UnitToCreate.EntityLastUpdateDate
    

    保持不变。

    要解决此问题,您必须实际将指针传递给指向扩展方法的指针。这是通过添加 ref 关键字来完成的:

        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;
            }
        }
    

    很遗憾,扩展方法不支持 ref 关键字。您的替代方法是将该方法用作标准静态方法:

        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;
            }
        }
    

    这样称呼它:

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

    理想情况下,您可以创建一个仅进行空值检查的不同扩展方法,然后自己设置变量:

        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;
        }
    

    并通过以下方式调用它:

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

    相当于(使用引用类型时):

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

    【讨论】:

    • 你的第二个想法对我不起作用。 DateTime 是一个结构,所以我不能传递对它的引用。 :(
    【解决方案3】:

    我认为更好的方法是使用 C# 4.0 ?? 运算符:

       var nullable;
    
       . . . 
    
       nullable = nullable ?? "defaultValue";
    

    你想达到什么目的?在我个人的工作习惯中,我非常沮丧的是,当我编写了一个扩展方法时,C#IDictionary 对象在引用的键不存在时抛出异常(dict['not here'] 抛出异常):

    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;
            }
    

    这是你所追求的吗?

    【讨论】:

    • 对于字典注释,你总是可以使用 TryGetValue,但是是的,它通常需要多行。
    • 事实上,在我的业务领域中,这是我想要的行为......如果我担心该值以前是否存在(而不是获取默认值),我会测试Contains 不仅更清晰,而且性能更高。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-15
    相关资源
    最近更新 更多