【问题标题】:Is there a way to call a method when any property of a class is set?设置类的任何属性时,有没有办法调用方法?
【发布时间】:2025-11-29 09:40:01
【问题描述】:

所以我要做的是在设置 C# 类中的任何属性时调用单个 propertyWasSet() 函数(相反,在获取时调用 propertyWasGot())。我还想知道调用了哪个属性的“get”。

我想维护一个“已设置”属性的字典,并检查“获取”操作是否已设置(如果尚未设置则抛出错误)。

我一直在查看 msdn 文档以了解反射、委托等...,但我不完全确定这是可能的。

有没有办法做到这一点?或者在调用其中一个可以在基类或其他东西中拦截的函数时触发事件?

【问题讨论】:

    标签: c# reflection properties delegates


    【解决方案1】:

    前一周我为 Set 写了一个拦截器,它可以很容易地扩展为 Get,它使用 RealProxy,这意味着你的基类需要从 MarshalByRefObject 派生。

    另一个奇特的选择是让你的类抽象,并使用反射发射来构造一个包含所有属性的具体类。

    您还可以查看代码生成器来解决这个问题或 PostSharp...

    此解决方案的性能并不出色,但对于大多数 UI 绑定来说应该足够快。它可以通过为代理调用生成 LCG 方法来改进。

    public interface IInterceptorNotifiable {
        void OnPropertyChanged(string propertyName);
    }
    
    /// <summary>
    /// A simple RealProxy based property interceptor
    /// Will call OnPropertyChanged whenever and property on the child object is changed
    /// </summary>
    public class Interceptor<T> where T : MarshalByRefObject, IInterceptorNotifiable, new() {
    
        class InterceptorProxy : RealProxy {
            T proxy; 
            T target;
            EventHandler<PropertyChangedEventArgs> OnPropertyChanged;
    
            public InterceptorProxy(T target)
                : base(typeof(T)) {
                this.target = target;
            }
    
            public override object GetTransparentProxy() {
                proxy = (T)base.GetTransparentProxy();
                return proxy;
            }
    
    
            public override IMessage Invoke(IMessage msg) {
    
                IMethodCallMessage call = msg as IMethodCallMessage;
                if (call != null) {
    
                    var result = InvokeMethod(call);
                    if (call.MethodName.StartsWith("set_")) {
                        string propName = call.MethodName.Substring(4);
                        target.OnPropertyChanged(propName);
                    } 
                    return result;
                } else {
                    throw new NotSupportedException();
                } 
            }
    
            IMethodReturnMessage InvokeMethod(IMethodCallMessage callMsg) {
                return RemotingServices.ExecuteMessage(target, callMsg);
            }
    
        }
    
        public static T Create() {
            var interceptor = new InterceptorProxy(new T());
            return (T)interceptor.GetTransparentProxy();
        }
    
    
        private Interceptor() {
        }
    
    }
    

    用法:

      class Foo : MarshalByRefObject, IInterceptorNotifiable {
            public int PublicProp { get; set; }
            public string lastPropertyChanged;
    
            public void OnPropertyChanged(string propertyName) {
                lastPropertyChanged = propertyName;
            }
    
        }
    
    
        [Test]
        public void TestPropertyInterception() {
    
            var foo = Interceptor<Foo>.Create();
            foo.PublicProp = 100;
    
            Assert.AreEqual("PublicProp", foo.lastPropertyChanged);
    
        }
    }
    

    【讨论】:

    • 这真的很有趣。我假设我可以在对象的构造函数中做 Create() 的东西,所以我不必显式地更改我所有对象的实例化。我要玩这个。非常感谢!
    • 我认为您确实需要为您的对象创建一个工厂,无论如何这可能是一个更好的做法(因为它允许您在以后交换对象实现并缓存对象实例)
    【解决方案2】:

    您可能需要查看PostSharp 来完成此类任务。它被设计为在 C#(或任何 .NET 语言)之上运行,并且具有额外的反射不会使您的代码混乱的好处。事实上,我不相信你能找到一个纯粹使用 C#/Reflection 的解决方案,而无需手动向每个属性添加代码,所以我肯定会推荐 PostSharp 作为要走的路。

    【讨论】:

      【解决方案3】:

      我认为您需要的与 WPF 依赖属性系统非常相似。你可能想看看它的实现。无论如何,您也可以将拦截代码添加到每个属性的getter和setter中。

      【讨论】:

      • 是的,我试图避免对每个属性添加检查,因为有多个属性,并且每个属性的代码几乎相同。
      【解决方案4】:

      如果您不自己创建它,就没有这样的东西。

      【讨论】:

      • 我考虑在运行时创建一个新类,并将所有类属性包装在一个可以做事的方法中,然后继续其正常业务,但同样,我不确定这是否可能,并且它很快变得复杂。
      【解决方案5】:

      您的请求的 SET 部分与 WPF 依赖属性系统非常相似。 但是 GET 部分非常不寻常,甚至在 WPF 依赖属性系统中也没有!

      【讨论】: