我想创建一个通用扩展方法,如果它们的值等于它们的默认值,它将为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;
中的行正在工作且值发生了变化但是当调试器退出扩展方法时,我看到{{1}的值仍然没有变化。
任何想法出了什么问题?
答案 0 :(得分:2)
您的代码中有两个引用。一个是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)复制整个对象。它不会改变结果(你不能分配新值),但需要提及。
答案 1 :(得分:0)
问题是,在您的扩展方法中,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;
答案 2 :(得分:0)
我认为更好的方法是使用C#4.0 ??
运算符:
var nullable;
. . .
nullable = nullable ?? "defaultValue";
你想要达到什么目的?在我自己的个人工作习惯中,当我没有引用一个引用键(IDictionary
引发异常)时,我对C#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;
}
这是你追求的那种吗?