2012-04-04 71 views
5

想象一下下面的類:動態通過反射生成屬性的getter/setter方法或類似

public class Settings 
{ 
    [FileBackedProperty("foo.txt")] 
    public string Foo { get; set; } 
} 

我希望能夠寫類似的東西上面,並有settings.Foo從一個文件「foo.txt」讀和settings.Foo = "bar"寫入「foo.txt」。

顯然這是一個簡化的例子,我不會在生產應用程序中做到上述,但還有其他一些例子,如果我希望Foo存儲在ASP.net會話狀態「foo」中,但我感到厭倦一遍又一遍寫如下代碼:

public int Foo 
{ 
    get 
    { 
     if (Session["foo"] != null) 
      return Convert.ToInt32(Session["foo"]); 
     else 
      // Throw an exception or return a default value 
    } 
    set 
    { 
     Session["foo"] = value; 
    } 
} 

(再次這個例子被簡化,我不會寫上面的代碼,其實我在撒謊,我上面的代碼和我合作,重構它,因此這個問題)

上面的例子沒有問題,除非你有50個不同的會話值都有類似的邏輯。那麼有沒有辦法將第二個屬性轉換成類似於第一個屬性? (?使用屬性和反射,或者一些其他方法)

+0

如果您使用的是Visual Studio,則應該查看[code snippets](http://msdn.microsoft.com/zh-cn/library/ms165392%28v=vs.90%29.aspx)。它們允許你輸入簡短的內容,擴展到所需的代碼,然後允許你填寫指定的區域。類似於現有的propg等 – 2012-04-04 20:26:22

+0

@JoshuaDrake這看起來和複製/粘貼一樣糟糕。如果我想改變實現呢?我仍然必須回去改變每一個物業。 – thelsdj 2012-04-04 20:28:48

+0

所以你想在運行時動態添加屬性?你有所有的服務器機器,還是對性能的期望很低?並不是說你不能這麼做,但我會強烈反對。如果你在運行時並不意味着什麼,那麼你將不得不重新生成任何代碼輸出。 – 2012-04-04 20:31:30

回答

1

如果你想避免編寫吸氣碼了這麼多,寫一個輔助方法:

public int Foo 
{ 
    get 
    { 
     return GetHelper<int>("foo"); 
    } 
    set 
    { 
     Session["foo"] = value; 
    } 
} 

public T GetHelper<T>(string name, T defaultValue = default(T)) 
{ 
    if (Session[name] != null) 
     return (T)Session[name]; 
    else 
    { 
     return defaultValue; 
    } 
} 

如果你有機會獲得動力,那麼你可以使用動態對象來包裝會話:

internal class DynamicSession : DynamicObject 
{ 
    private HttpSessionState_session; 

    public DynamicSession() 
    { 
     _session = HttpContext.Current.Session; 
    } 

    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
     if (_session[binder.Name] != null) 
     { 
      result = _session[binder.Name]; 
      return true; 
     } 
     result = null; 
     return false; 
    } 

    public override bool TrySetMember(SetMemberBinder binder, object value) 
    { 
     _session[binder.Name] = value; 
     return true; 
    } 
} 

然後你可以使用它像這樣:

dynamic session = new DynamicSession(); 
    //These properties are "magically" put in and taken out of session! 
//get 
int foo = session.Foo; 
//set 
session.Foo = 3; 

Resharper中的最後一個選項類似於Live Templates,使得輸入代碼變得容易很多。

+0

嗯..但它*似乎*的東西OP的試圖避免其實... – Tigran 2012-04-04 20:35:16

+0

@Tigran是的,但也許它是不可能的。上述_is_較短並且重複較少。看起來很糟糕,沒有一個好的方法來做我想做的事情,因爲它可以擺脫大量的重複。 – thelsdj 2012-04-04 20:38:54

+0

@thelsdj:看到我的答案,可以給你一個很好的選擇,imo。 – Tigran 2012-04-04 20:42:43

1

你所要做的就是所謂的「面向方面編程」,它對於某些任務來說是相對常見的,在這些任務中必要的代碼會被重複多次,只需要很小的改動。你的例子當然有資格。

基本思路如下;你創建了一個屬性,你可以使用它來裝飾類或類成員。該屬性爲CLR中的消息傳遞系統定義了一個「上下文」,允許您將方法攔截器掛接到調用該方法時運行的方法。

瞭解所涉及的重大性能問題;具有由屬性裝飾的成員的對象必須從MarshallByRefObject或ContextBoundObject繼承;即使您實際上沒有進行任何屬性裝飾,其中任何一種都會在運行時產生10倍左右的性能。

下面是一些示例代碼:http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/3/

您還可以使用動態代理爲「對飛」基於屬性的裝飾或其他基於反射的信息來創建對象。這是C#開發人員認爲理所當然的技術背後的技術,比如ORM,IoC框架等等。您基本上會使用Castle DynamicProxy這樣的東西來創建一個看起來像您的基礎對象的對象,但重寫了使用具有基於文件的填充/持久性邏輯的屬性修飾的屬性的定義。

3

另一個選擇可能是你使用PostSharp。 您可以定義屬性,並在最終代碼中注入IL,所以它不會更改源代碼。這有它的壞處和貨物。

此產品不是免費的。

部分Getting started提示。

希望這會有所幫助。

+0

你提到它注入'IL',是在編譯時或應用程序啓動時或什麼?我猜測PostSharp會解決使用ContextBoundObject的AOP的性能問題嗎? – thelsdj 2012-04-04 20:59:38

+0

這是一個編譯時間。當然,沒有問題沒有善良:)主要,我會mantion,imo,是運行的最終二進制文件不是你期望通過*你的*代碼期望。當然,也有一些性能問題,但這是必須在具體實施中衡量的。 – Tigran 2012-04-04 21:02:44

+0

我的意思是這是工業級軟件,所以值得關注。 – Tigran 2012-04-04 21:03:57

4

我知道這不是你(也是我)所需要的;但這是最接近的,而不使用第三方庫。您可以更改獲取設置方法的邏輯,併爲GetProperty和GetCustomAttributes方法添加一些屬性,或者如果您已經擁有可以編寫的基類,則可以在輔助類中將set方法設置爲靜態。同樣沒有完美的答案,也可能有糟糕的表現,但至少它降低你的代碼複製並粘貼(:

注意:爲了讓性能以防止編譯器內聯它們的虛擬是很重要的

public class SampleClass : SessionObject 
{ 
    [Session(Key = "SS_PROP")] 
    public virtual int SampleProperty 
    { 
     get { return get(); } 
     set { set(value); } 
    } 

    [Session(Key = "SS_PROP2")] 
    public virtual string SampleProperty2 
    { 
     get { return get(); } 
     set { set(value); } 
    } 
} 

[AttributeUsage(AttributeTargets.Property)] 
public class SessionAttribute : Attribute 
{ 
    public string Key { get; set; } 
} 

public abstract class SessionObject 
{ 
    Dictionary<string, object> Session = new Dictionary<string, object>(); 

    protected void set(object value) 
    { 
     StackFrame caller = new StackFrame(1); 
     MethodBase method = caller.GetMethod(); 
     string propName = method.Name.Substring(4); 
     Type type = method.ReflectedType; 
     PropertyInfo pi = type.GetProperty(propName); 
     object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true); 
     if (attributes != null && attributes.Length == 1) 
     { 
      SessionAttribute ssAttr = attributes[0] as SessionAttribute; 
      Session[ssAttr.Key] = value; 
     } 
    } 

    protected dynamic get() 
    { 
     StackFrame caller = new StackFrame(1); 
     MethodBase method = caller.GetMethod(); 
     string propName = method.Name.Substring(4); 
     Type type = method.ReflectedType; 
     PropertyInfo pi = type.GetProperty(propName); 
     object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true); 
     if (attributes != null && attributes.Length == 1) 
     { 
      SessionAttribute ssAttr = attributes[0] as SessionAttribute; 
      if (Session.ContainsKey(ssAttr.Key)) 
      { 
       return Session[ssAttr.Key]; 
      } 
     } 
     return default(dynamic); 
    } 
} 
0

您還可以使用DynamicProxy nuget package from Castle.Core實現這一行爲。

您可以攔截你的類的所有虛擬財產調用get和set方法。但是你要修改的屬性getter和setter方法必須是虛擬的。

我在這裏提供了更完整的答案: https://stackoverflow.com/a/48764825/5103354 和一個要點here。應觀察

以下行爲:

[Fact] 
    public void SuccessFullyRegisterGetAndSetEvents() 
    { 
     ProxyGenerator generator = new ProxyGenerator(); 
     var tracked = generator.CreateClassProxy<TrackedClass>(new GetSetInterceptor()); 
     tracked.SomeContent = "some content"; 
     Assert.Single(tracked.GetEvents()); 
     var eventAfterSomeContentAssigned = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType); 
     Assert.Equal("some content", eventAfterSomeContentAssigned.Value); 
     Assert.Equal("SomeContent", eventAfterSomeContentAssigned.PropertyInfo.Name); 
     tracked.SomeInt = 1; 
     Assert.Equal(2, tracked.GetEvents().Count); 
     var eventAfterSomeIntAssigned = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType); 
     Assert.Equal(1, eventAfterSomeIntAssigned.Value); 
     Assert.Equal("SomeInt", eventAfterSomeIntAssigned.PropertyInfo.Name); 
     var x = tracked.SomeInt; 
     Assert.Equal(3, tracked.GetEvents().Count); 
     var eventAfterSomeIntAccessed = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Get, eventAfterSomeIntAccessed.EventType); 
     Assert.Equal(1, eventAfterSomeIntAccessed.Value); 
     Assert.Equal("SomeInt", eventAfterSomeIntAccessed.PropertyInfo.Name); 
    } 

希望這有助於。