有什么方法可以使用 DynamicProxy 在 C# 中拦截对内部字典的访问?

Any way to intercept access to internal Dictionary in C# with DynamicProxy?

我有一个简单的 class 和拦截器设置(主要基于 freezable 示例),

using System;
using Castle.Core.Logging;
using System.Collections.Generic;
using System.Reflection;
using Castle.DynamicProxy;

[Serializable]
public class Pet
{
    public virtual string Name { get; set; }
    public virtual Dictionary<String, int> Dict { get; set; }

    public Pet()
    {
        Dict = new Dictionary<string, int>();
    }

    public override string ToString()
    {
        return string.Format("Name: {0}, Age: {1}", Name, Dict);
    }

    public object getFieldDirect(string name)
    {
        return GetType().BaseType.GetField(
                name,
                BindingFlags.Instance | BindingFlags.NonPublic
            ).GetValue(this);
    }
}

public class PetXample
{
    public PetXample()
    {
        Console.WriteLine( new NullLogger() );

        Pet rex;

        rex = Something.MakeSomething<Pet>();

        rex.Name = "Rex";
        rex.Dict["key"] = 2;
        rex.ToString();

        Console.WriteLine( rex.ToString() );
    }
}

public static class Something
{
    private static readonly ProxyGenerator _generator = new ProxyGenerator(new PersistentProxyBuilder());

    public static TSomething MakeSomething<TSomething>() where TSomething : class, new()
    {
        TSomething proxy = _generator.CreateClassProxy<TSomething>( new MyInterceptor() );
        return proxy;
    }
}

[Serializable]
public class MyInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        if( invocation.Method.Name.StartsWith("set_") )
        {
            string shortName = invocation.Method.Name.Substring(4);
            string intName = "<" + shortName + ">k__BackingField";
            object obj = invocation.InvocationTarget;
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            FieldInfo fi = invocation.TargetType.GetField(
                intName,
                flags
            );
            Console.WriteLine( shortName + ": " + fi.FieldType + " - " + fi.GetValue(obj) );
        }
    }
}

这完全符合我的要求,但访问 Pet 的内部字典键时除外。有没有什么方法可以自动拦截该访问(我假设没有),或者我最好只限制对私有的访问并只允许通过函数访问?

执行 rex.Dict["key"] = 2; 类似于

    Dictionary<String, Int32> dico = rex.Dict;
    dico["key"] = 2;

如您所见,调用 rex.Dict["key"] = 2; 不会在 rex 实例上调用任何 set 方法。你的拦截器不会拦截任何东西。

要实现您的目标,您还必须代理词典。

例如:

    [Serializable]
    public class Pet
    {
        public virtual String Name { get; set; }
        public virtual IDictionary<String, Int32> Dict { get; set; }

        public Pet()
        {
            this.Dict = Something.MakeSomething<IDictionary<String, Int32>>(new Dictionary<String, Int32>());
        }

        public override String ToString()
        {
            return String.Format("Name: {0}, Age: {1}", Name, Dict);
        }
    }

    public static class Something
    {
        private static readonly ProxyGenerator _generator = new ProxyGenerator(new PersistentProxyBuilder());

        public static TSomething MakeSomething<TSomething>() where TSomething : class, new()
        {
            TSomething proxy = _generator.CreateClassProxy<TSomething>(new MyInterceptor());
            return proxy;
        }

        public static TSomething MakeSomething<TSomething>(TSomething instance) where TSomething : class
        {
            TSomething proxy = _generator.CreateInterfaceProxyWithTargetInterface<TSomething>(instance, new MyInterceptor());
            return proxy;
        }
    }

    [Serializable]
    public class MyInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            invocation.Proceed();

            if (invocation.Method.IsSpecialName && invocation.Method.Name.StartsWith("set_"))
            {
                PropertyInfo pi = invocation.TargetType.GetProperty(invocation.Method.Name.Substring(4), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

                Console.WriteLine("{0}[{1}]({2})", pi.Name, pi.PropertyType, String.Join(" - ", invocation.Arguments.Select(a => a.ToString()).ToArray()));
            }

        }
    }