通过反射或类似操作动态生成属性getter/setter
本文关键字:属性 getter setter 动态 操作 反射 | 更新日期: 2023-09-27 18:30:06
想象一下下面的类:
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个不同的会话值,它们都具有相似的逻辑。那么,有没有什么方法可以将第二个属性转换为与第一个属性类似的属性?(使用属性和反射,或者其他方法?)
我知道这不是你(也是我)所需要的;但这是在不使用第三方库的情况下最接近的。您可以更改get&设置方法并为GetProperty和GetCustomAttributes方法添加一些cahcing,或者如果您已经有了基类,则可以编写get&在助手类中将方法设置为静态。同样,这不是一个完美的答案,也可能有一个糟糕的性能,但至少它减少了你复制和粘贴的代码(:
注意:为了防止编译器内联属性,使其成为虚拟属性是很重要的。
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);
}
}
另一个选择是使用PostSharp。您定义了属性,它在最终代码中注入了IL
,所以它不会更改源代码。它有缺点也有优点。
此产品不是免费的。
一些入门技巧。
希望这能有所帮助。
如果您希望在运行时执行此操作,请参阅以下Dr.Dobbs文章在运行时使用Reflection.Emit.生成代码
您正在尝试做的被称为"面向方面编程",对于某些任务来说,这是相对常见的,因为在这些任务中,必要的代码只需非常小的更改就可以重复多次。你的榜样当然是合格的。
其基本思想如下:;您可以创建一个属性,用于装饰类或类成员。该属性为CLR中的消息传递系统定义了一个"上下文",允许您将方法拦截器挂接到调用时将运行的方法上。
了解其中涉及重大绩效打击;成员由属性修饰的对象必须继承自MarshallByRefObject或ContextBoundObject;其中任何一个都会在运行时对对象的性能造成大约10倍的影响,即使您实际上没有进行任何属性装饰。
以下是一些示例代码:http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/3/
也可以使用"动态代理"基于属性装饰或其他基于反射的信息"动态"创建对象。这是C#开发人员认为理所当然的许多东西背后的技术,比如ORM、IoC框架等。你基本上会使用Castle DynamicProxy之类的东西来创建一个看起来像你的基本对象的对象,但已经覆盖了用具有基于文件的填充/持久性逻辑的属性装饰的属性的定义。
如果你想避免写太多getter代码,可以写一个辅助方法:
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,这样可以更容易地键入代码。
您还可以使用Castle.Core中的DynamicProxy-nuget包来实现这种行为。
您可以拦截对类的所有虚拟属性的Get和Set方法的调用。但是,要修改的所有属性getter和setter都必须是虚拟的。
我在这里提供了一个更完整的答案:https://stackoverflow.com/a/48764825/5103354这里有要点。
应遵守以下行为:
[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);
}
希望这能有所帮助。