动态通过反射生成属性的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个不同的会话值都有类似的逻辑。那么有没有办法将第二个属性转换成类似于第一个属性? (?使用属性和反射,或者一些其他方法)
如果你想避免编写吸气码了这么多,写一个辅助方法:
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,使得输入代码变得容易很多。
你所要做的就是所谓的“面向方面编程”,它对于某些任务来说是相对常见的,在这些任务中必要的代码会被重复多次,只需要很小的改动。你的例子当然有资格。
基本思路如下;你创建了一个属性,你可以使用它来装饰类或类成员。该属性为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提示。
希望这会有所帮助。
我知道这不是你(也是我)所需要的;但这是最接近的,而不使用第三方库。您可以更改获取设置方法的逻辑,并为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);
}
希望这有助于。
如果您使用的是Visual Studio,则应该查看[code snippets](http://msdn.microsoft.com/zh-cn/library/ms165392%28v=vs.90%29.aspx)。它们允许你输入简短的内容,扩展到所需的代码,然后允许你填写指定的区域。类似于现有的propg等 – 2012-04-04 20:26:22
@JoshuaDrake这看起来和复制/粘贴一样糟糕。如果我想改变实现呢?我仍然必须回去改变每一个物业。 – thelsdj 2012-04-04 20:28:48
所以你想在运行时动态添加属性?你有所有的服务器机器,还是对性能的期望很低?并不是说你不能这么做,但我会强烈反对。如果你在运行时并不意味着什么,那么你将不得不重新生成任何代码输出。 – 2012-04-04 20:31:30