2012-11-24 38 views
2

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

,所以我有以下代码:

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的值仍未解除。

任何想法出了什么问题?

+1

相关:http://stackoverflow.com/questions/1259103/doesnt-c-sharp-extension-methods-allow-passing-parameters-by-reference。 –

回答

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)复制整个对象。它不会改变结果(你不能指定新值),但需要提及。

+0

是否有解决方法? – Mortalus

+1

很有创意!非常感谢 !!! – Mortalus

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; 
+0

你的第二个想法对我不起作用。 DateTime是一个结构,所以我不能传递给它的引用。 :( – Mortalus

0

我认为更好的办法是使用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; 
     } 

这是那种你以后的事情?

+0

至于字典的评论,你可以随时使用TryGetValue,但是,它通常需要多行。 –

+0

事实上,在我的业务领域,这是我想要的行为....如果我担心无论该值是否先前存在(而不是获取默认值),我都会使用“Contains”进行测试,这不仅更清晰,而且更高性能。 – SAJ14SAJ

相关问题