Search code examples
c#castle-dynamicproxy

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


I've a simple class and interceptor set up (heavily based on the freezable example),

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) );
        }
    }
}

Which works exactly how I want it to, except when accessing the internal Dictionary keys of a Pet. Is there any way to automatically intercept that access as well (I'm assuming not), or am I better off just restricting access to private with them and only allowing access via functions?


Solution

  • Executing rex.Dict["key"] = 2; is similar to

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

    As you can see, calling rex.Dict["key"] = 2; don't call any set method on rex instance. Your interceptor won't intercept anything.

    To achieve what you want to do, you will have to proxy the Dictionary also.

    For example :

        [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()));
                }
    
            }
        }