繁体   English   中英

setIfNull 扩展方法不会给对象设置值

[英]setIfNull Extention Method will not set the value to the object

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

所以我有以下代码:

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的值保持不变。

任何想法出了什么问题?

您的代码中有两个引用。 一个是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)复制整个对象。 它不会改变结果(您不能分配新值),但需要提及。

问题是,在您的扩展方法中, 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;

我相信更好的方法是使用 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;
        }

这是你追求的那种东西吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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