2014-01-09 42 views
2

有被許多GUI框架,以確保事情真的很酷的圖案編碼是否正確無誤:重載通用擴展方法與約束 - 的Func鍵模式

interface IBase1 {} 
interface IBase2 {} 

class Base1 : IBase1 
{ 
    public int x { get; set; } 
} 
class Base2 : IBase2 
{ 
    public int y { get; set; } 
} 

static class Helpers 
{ 
    public static void ToProp<T,Y> (this T obj, Func<T, Y> getter) 
    { 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var b1 = new Base1(); 
     var b2 = new Base2(); 

     b1.ToProp(b => b.x); 
     b2.ToProp(b => b.y); 
    } 
} 

這裏的美妙的事情是,當你鍵入b => b.x,視覺Studio會給你IntelliSense,如果你試圖訪問一個不正確的屬性,編譯器會報錯。我經常在MVVM框架中看到這種情況。他們通常會將b => b.x作爲表達式樹並解析出參數的名稱,以便正確地引發通知屬性更改消息。

我想延長這一點,並用下面的取代ToProp定義,基本上具有取決於基本接口上的兩個代碼路徑:

static class Helpers 
{ 
    public static void ToProp<T,Y> (this T obj, Func<T, Y> getter) 
     where T : IBase1 
    { 
     // Do something custom for 1 
    } 

    public static void ToProp<T, Y>(this T obj, Func<T, Y> getter) 
     where T : IBase2 
    { 
     // Do something custom for 2 
    } 
} 

這將不會編譯原樣 - 兩個ToProp調用都會因模糊的方法解析錯誤而失敗。這是一個衆所周知的SO問題 - 對象約束不是方法解析過程的一部分(例如參見Lipert的blog)。

但我不禁想知道是否沒有辦法。例如,我嘗試用this Base1 obj代替this T obj,但是在這種情況下,您將失去對財產分辨率的支持,也可以編寫b1.ToProp(b => b.y)。我想,這可能會遇到運行時異常。我也嘗試了隱式轉換 - 但不幸的是,這不是方法解析過程的一部分。

這是因爲我擴展了ReactiveUI框架以使用Caliburn.Micro。 ReactiveUI有這個非常好的擴展方法ToProperty,它需要一個ReactiveUI ViewModel。稍作修改後,我可以修改該代碼以使用Caliburn.Micro視圖模型。但是,然後我遇到了上述模糊的方法問題。在此期間,我只是致電Caliburn.Micro方法ToPropertyCM

任何人都知道我應該追求這樣的工作的聰明大道?並且可擴展到新的基類類型?

編輯修正了一個例子,表明它是一個我感興趣的簡單接口。順便說一句,我試過包裝方法b.c.如果我理解方法解析,它應該允許您對解析過程中的模板參數的類型進行約束檢查。但是,正如我所提到的,隱式類型轉換在解析過程中不起作用。

+0

正如你在文中提到它,你真的只是想提取表達的財產? – poke

+0

是的,這就是我如何得到屬性名稱...只是解析表達式樹(大多數實現目前有一個相當有限的解析能力)。 – Gordon

回答

2

只是不要讓通用方法相對於該類型:

public static void ToProp<Y>(this Base1 obj, Func<Base1, Y> getter) 
{ 
    // Do something custom for 1 
} 

public static void ToProp<Y>(this Base2 obj, Func<Base2, Y> getter) 
{ 
    // Do something custom for 2 
} 

如果這些方法是通用對於該類型是很重要的,那麼你就需要一些改變簽名解決歧義的方法。這樣做的最有效方法是更改​​名稱。如果該行爲被個性化這兩種類型,然後他們在概念上做的事情至少略有不同,所以你應該能夠反映在方法的名稱。

+1

此解決方案已被OP丟棄,因爲您丟失了obj的強類型。您不能在委託中調用派生類的成員。 –

+0

@ThomasLevesque我不確定這是否已被丟棄的解決方案:它看起來OP不喜歡的解決方案是「ToProp (this Base1 obj,Func getter)」 – dasblinkenlight

+1

@ThomasLevesque編輯好的段落地址在這種情況下,雖然按照dasb的評論,但我不太確定這是否是他丟棄的情況。 – Servy

1

至於你提到,你實際上只是在解析表達式來獲得屬性名稱感興趣,我會爲您呈現一個不同的方法,而不是專注於你的代碼,作爲Servy的答案的討論表明,不會真的有用。

所以,我這樣做我自己很多關於MVVM。我的視圖模型實現了INotifyPropertyChanged並引發了PropertyChanged事件,我需要指定在事件參數中更改的屬性的名稱。由於這是一個字符串,因此不存在固有的檢查,即屬性名稱實際上是正確的。就像你說的,我使用lambda表達式來指定類型安全問題中的屬性(帶有IntelliSense支持),然後解析表達式樹來提取屬性名稱。

由於這是我只需要INotifyPropertyChanged,我在我的基本視圖模型中實現,它實現了接口和用lambda表達式引發事件的快速方法。

所以我實際上並沒有使用擴展方法。這有好處,我不需要知道業主的類型。例如,如果我想爲something.Name投擲事件,我不需要知道something是什麼類型。而不是運行這個的:

viewModel.OnPropertyChanged(viewModel.GetPropertyNameFor(vm => vm.Name)); 

我只是做

viewModel.OnPropertyChanged(() => viewModel.Name); 
// or actually 
this.OnPropertyChanged(() => Name); 

所以表達我們正在尋找看起來像這樣:() => obj.Property。那是一個Expression<Func<T>>,其中T是屬性的類型 - 這實際上並不令我們感興趣。

萃取實際上發生在一個靜態方法,看起來像這樣:

static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) 
{ 
    if (propertyExpression == null) 
     throw new ArgumentNullException("propertyExpression"); 

    MemberExpression memberExpression = propertyExpression.Body as MemberExpression; 
    if (memberExpression == null) 
     throw new ArgumentException("The expression is not a member access expression.", "propertyExpression"); 

    PropertyInfo property = memberExpression.Member as PropertyInfo; 
    if (property == null) 
     throw new ArgumentException("The member access expression does not access a property.", "propertyExpression"); 

    return memberExpression.Member.Name; 
} 

而這已經全部有做:只是在一些輔助方法

var obj = new { 
    Foo = 123, 
    Bar = "baz" 
}; 

Console.WriteLine(ExtractPropertyName(obj.Foo)); // Foo 
Console.WriteLine(ExtractPropertyName(obj.Bar)); // Bar 

所有跟隨允許呼叫OnPropertyChanged(Expression<Func<T>> propertyExpression)等的基本視圖模型


Y OU實際上可以使功能的擴展方法只是通過改變它的簽名:

static string ExtractPropertyName<T> (this INotifyPropertyChanged obj, Expression<Func<T>> propertyExpression) 
{ … } 

然後,你可以調用的方法實現INotifyPropertyChanged,你的框架很可能不會在任何對象上。

你舉的例子可能是這樣的,那麼:

var b1 = new Base1(); 
var b2 = new Base2(); 

// in a static Utils class 
Utils.ExtractPropertyName(() => b1.x); 

// or as an extension method to INPC 
someViewModel.ExtractPropertyName(() => b2.y); 
+0

聰明,謝謝!我已經有了表達式編碼 - 但是在基類中實現這個想法是非常好的。在ExtractPropertyName中,您是否需要進行類型檢查以確保它適用於該對象? – Gordon

+0

在我的特殊情況下,這不是一個選項 - 我正在爲現有框架添加功能 - 雖然我可以重新實現它們的基類(它們使用接口),但這是很多工作。我也沒有真正控制其他人的框架。但是如果我完全控制,這將工作,因爲我會在兩個不同的對象上實現該方法,然後根據定義爲每個對象運行自定義代碼。 – Gordon

+0

'ExtractPropertyName'不需要對該對象進行類型檢查。它要求你傳遞一個'Func '表達式,並且裏面的檢查確保表達式實際上是一個引用屬性的成員表達式。每個引用屬性的成員表達式都會自動工作 - 對象沒有其他類型的限制。 – poke