2012-11-24 41 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

相關問題