动态通过反射生成属性的getter/setter方法或类似

问题描述:

想象一下下面的类:动态通过反射生成属性的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

如果你想避免编写吸气码了这么多,写一个辅助方法:

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

如果您希望在运行时执行此操作,请参阅以下Dr. Dobbs文章Generating Code at Run Time With Reflection.Emit

你所要做的就是所谓的“面向方面编程”,它对于某些任务来说是相对常见的,在这些任务中必要的代码会被重复多次,只需要很小的改动。你的例子当然有资格。

基本思路如下;你创建了一个属性,你可以使用它来装饰类或类成员。该属性为CLR中的消息传递系统定义了一个“上下文”,允许您将方法拦截器挂接到调用该方法时运行的方法。

了解所涉及的重大性能问题;具有由属性装饰的成员的对象必须从MarshallByRefObject或ContextBoundObject继承;即使您实际上没有进行任何属性装饰,其中任何一种都会在运行时产生10倍左右的性能。

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

您还可以使用动态代理为“对飞”基于属性的装饰或其他基于反射的信息来创建对象。这是C#开发人员认为理所当然的技术背后的技术,比如ORM,IoC框架等等。您基本上会使用Castle DynamicProxy这样的东西来创建一个看起来像您的基础对象的对象,但重写了使用具有基于文件的填充/持久性逻辑的属性修饰的属性的定义。

另一个选择可能是你使用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

我知道这不是你(也是我)所需要的;但这是最接近的,而不使用第三方库。您可以更改获取设置方法的逻辑,并为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); 
    } 
} 

您还可以使用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); 
    } 

希望这有助于。