【问题标题】:Dynamically generate property getter/setter through reflection or similar通过反射或类似方法动态生成属性 getter/setter
【发布时间】:2012-04-18 14:24:24
【问题描述】:

想象以下类:

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 个不同的会话值,它们都有相似的逻辑。那么有没有办法可以将第二个属性转换为类似于第一个属性的东西? (使用属性和反射,或者其他方法?)

【问题讨论】:

  • 如果你使用 Visual Studio,你应该看看code snippets。它们允许您键入简短的内容,扩展为所需的代码,然后允许您填写指定的区域。类似于现有的propg等
  • @JoshuaDrake 这似乎和复制/粘贴一样糟糕。如果我想更改实现怎么办?我仍然需要返回并单独更改每个属性。
  • 所以你想在运行时动态添加属性?您是否拥有所有服务器机器,或者对性能的期望很低?并不是说你不能这样做,但我强烈警告不要这样做。如果您的意思不是在运行时,那么无论如何您都必须重新生成任何代码输出。

标签: c# reflection metaprogramming


【解决方案1】:

我知道这不是您(以及我)所需要的;但这是不使用第三方库的最接近的。您可以更改 get&set 方法的逻辑并为 GetProperty 和 GetCustomAttributes 方法添加一些cahcing,或者如果您已经有一个基类,您可以在帮助类中将 get&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);
    }
}

【讨论】:

    【解决方案2】:

    另一个选项可能适合您使用PostSharp。 您定义属性并在最终代码中注入IL,因此它不会更改您的源代码。它有它的坏处和它的优点。

    此产品不是免费的。

    一些Getting started 的提示。

    希望这会有所帮助。

    【讨论】:

    • 你提到它注入IL,是在编译时还是应用程序启动时还是什么?我猜 PostSharp 然后解决了使用 ContextBoundObject 的 AOP 的性能问题?
    • 这是一个编译时间。自然,没有问题就没有好处 :) 主要的,我要强调,imo,运行的最终二进制文件不是您通过查看 your 代码所期望的那个。当然,也存在一些性能问题,但必须根据您的具体实施来衡量。
    • 我的意思是这是工业级软件,所以值得关注。
    • 看这里:sharpcrafters.com/purchase/compare 似乎我可以使用免费的“入门”版做我需要的事情。我可能会尝试一下,看看是否可行。
    • @thelsdj:这是个好主意。是的,有免费试用(应该)。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      您正在尝试做的事情称为“面向方面的编程”,对于某些任务而言,这相对常见,否则必要的代码会被重复多次,而只需进行非常小的更改。你的例子当然是合格的。

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

      了解这会对性能造成重大影响;成员被属性修饰的对象必须继承自 MarshallByRefObject 或 ContextBoundObject;这些中的任何一个都会在运行时对对象的性能造成大约 10 倍的影响,即使您实际上没有进行任何属性装饰。

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

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

      【讨论】:

        【解决方案5】:

        如果你想避免写这么多的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,只是让输入代码更容易。

        【讨论】:

        • @Tigran 是的,但也许这是不可能的。上面的 更短,重复更少。没有一种好方法可以做我想做的事,这似乎很糟糕,因为它会消除大量重复。
        • @Tigran 我包含了一个 sn-p,它利用 DLR 和动态来实现这些结果。
        • @vcsjones 动态版本非常好,除了我现在使用的是 3.5 并且我还需要能够控制属性名称到会话变量的转换(尽管这可能是用查找表或其他东西完成)。为可以选择此选项的其他人 +1。
        【解决方案6】:

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

        希望这会有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-12-24
          相关资源
          最近更新 更多