在 运行 时间将方法分配给对象 - 设计模式

Assigning methods to object at run-time - Design Pattern

我在我的 C# 代码中创建了一个架构,它完全符合我的要求,但似乎很难在长期 运行 中维护并且我 希望 我可以指出一种设计模式/更好的架构。

我创建了一个对象 Test,它再次完美地完成了我所需要的,它具有以下结构:

class Test
{
    public static Dictionary<string, Func<Test, object>> MethodDictionary;

    public double Var1;
    public double Var2;

    private Lazy<object> _test1;
    public object Test1 { get { return _test1.Value; } }

    private Lazy<object> _test2;
    public object Test2 { get { return _test2.Value; } }

    public Test()
    {
        _test1 = new Lazy<object>(() => MethodDictionary["Test1"](this), true);
        _test2 = new Lazy<object>(() => MethodDictionary["Test2"](this), true);
    }
}

这让我可以做的是,在 运行 时为我的 Test 对象和 2 个属性 Test1Test2 分配一个函数字典将使用加载到其中的函数到 return 值。

实施看起来有点像下面这样:

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, Func<Test, object>> MethodDictionary = new Dictionary<string,Func<Test,object>>();
        MethodDictionary.Add("Test1", TestMethod1);
        MethodDictionary.Add("Test2", TestMethod2);

        Test.MethodDictionary = MethodDictionary;

        var x = new Test() { Var1 = 20, Var2 = 30 };
        Console.WriteLine(x.Test1.ToString());
        Console.WriteLine(x.Test2.ToString());
        Console.ReadKey();
    }

    private static object TestMethod1(Test t)
    { return t.Var1 + t.Var2; }

    private static object TestMethod2(Test t)
    { return t.Var1 - t.Var2; }

}

它工作得很好,并且已证明对大型 Test 对象集非常有效。

我的挑战是,如果我想在我的 Test class 中添加一个新方法,我需要添加:

  1. private Lazy<object> _myNewMethod;
  2. public object MyNewMethod { get { return _myNewMethod.Value; } }
  3. 使用要在字典中查找的键更新构造函数

而且,虽然这很简单,但我希望有一个单行加载项(可能是某种形式的自定义对象)或直接从字典中读取属性,而无需在全部.

有什么想法吗?任何帮助都会很棒!!!

谢谢!!!

我不知道你想做什么,但我认为你可以使用像这样更简单的方法:

class Test
{
  public static Dictionary<string, Func<Test, object>> MethodDictionary;
  public double Var1;
  public double Var2;
}

调用函数很简单:

static void Main(string[] args)
{
    Dictionary<string, Func<Test, object>> MethodDictionary = new Dictionary<string,Func<Test,object>>();
    MethodDictionary.Add("Test1", TestMethod1);
    MethodDictionary.Add("Test2", TestMethod2);

    Test.MethodDictionary = MethodDictionary;

    var x = new Test() { Var1 = 20, Var2 = 30 };
    Console.WriteLine(Test.MethodDictionary["Test1"](x).ToString());
    Console.WriteLine(Test.MethodDictionary["Test2"](x).ToString());
    Console.ReadKey();
}

看图书馆https://github.com/ekonbenefits/impromptu-interface。 有了它并使用 DynamicObject,我编写了示例代码来展示如何简化添加新方法:

public class Methods
{
    public Methods()
    {
        MethodDictionary = new Dictionary<string, Func<ITest, object>>();
        LazyObjects = new Dictionary<string, Lazy<object>>();
    }

    public Dictionary<string, Func<ITest, object>> MethodDictionary { get; private set; }

    public Dictionary<string, Lazy<object>> LazyObjects { get; private set; }
}

public class Proxy : DynamicObject
{
    Methods _methods;
    public Proxy()
    {
        _methods = new Methods();
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = _methods.LazyObjects[binder.Name].Value;            

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _methods.MethodDictionary[binder.Name] = (Func<ITest, object>)value;
        _methods.LazyObjects[binder.Name] = new Lazy<object>(() => _methods.MethodDictionary[binder.Name](this.ActLike<ITest>()), true);
        return true;                        
    }

}

//now you can add new methods by add single method to interface 
public interface ITest
{
    object Test1 { get; set; }
    object Test2 { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var x = new Proxy().ActLike<ITest>();

        x.Test1 = new Func<ITest, object>((y) => "Test1");
        x.Test2 = new Func<ITest, object>((y) => "Test2");


        Console.WriteLine(x.Test1);
        Console.WriteLine(x.Test2);
    }
}

您可以实现所需行为的方法之一是使用类似于微型 IoC 框架的东西进行字段注入,并根据您的特定用例进行调整。

为了让事情变得更简单,减少输入具体的 classes 并使事情类型安全,我们引入了 LazyField 类型:

public class LazyField<T>
{
    private static readonly Lazy<T> Default = new Lazy<T>();
    private readonly Lazy<T> _lazy;

    public LazyField() : this(Default) { }
    public LazyField(Lazy<T> lazy)
    {
        _lazy = lazy;
    }

    public override string ToString()
    {
        return _lazy.Value.ToString();
    }

    public static implicit operator T(LazyField<T> instance)
    {
        return instance._lazy.Value;
    }
}

此外,我们定义了一个抽象基础class,以确保在构建时创建这些字段:

public abstract class AbstractLazyFieldHolder
{
    protected AbstractLazyFieldHolder()
    {
        LazyFields.BuildUp(this); // ensures fields are populated.
    }
}

暂时跳过这是如何实现的(在下面进一步解释),这允许通过以下方式定义您的 Test class:

public class Test : AbstractLazyFieldHolder
{
    public double Var1;
    public double Var2;
    public readonly LazyField<double> Test1;
    public readonly LazyField<double> Test2;
}

请注意,这些字段是不可变的,在构造函数中初始化。现在,对于您的用法示例,下面的代码片段显示了 "new way" 这样做的过程:

LazyFields.Configure<Test>()
    // We can use a type-safe lambda
    .SetProvider(x => x.Test1, inst => inst.Var1 + inst.Var2)
    // Or the field name.
    .SetProvider("Test2", TestMethod2);

var x = new Test() { Var1 = 20, Var2 = 30 };
Console.WriteLine(x.Test1);
double test2Val = x.Test2; // type-safe conversion
Console.WriteLine(test2Val);

// Output:
// 50
// -10

下面的class提供了支持配置和注入这些字段值的服务。

public static class LazyFields
{
    private static readonly ConcurrentDictionary<Type, IBuildUp> _registry = new ConcurrentDictionary<Type,IBuildUp>();

    public interface IConfigureType<T> where T : class
    {
        IConfigureType<T> SetProvider<FT>(string fieldName, Func<T, FT> provider);
        IConfigureType<T> SetProvider<F, FT>(Expression<Func<T, F>> fieldExpression, Func<T, FT> provider) where F : LazyField<FT>;
    }

    public static void BuildUp(object instance)
    {
        System.Diagnostics.Debug.Assert(instance != null);
        var builder = _registry.GetOrAdd(instance.GetType(), BuildInitializer);
        builder.BuildUp(instance);
    }

    public static IConfigureType<T> Configure<T>() where T : class
    {
        return (IConfigureType<T>)_registry.GetOrAdd(typeof(T), BuildInitializer);
    }

    private interface IBuildUp 
    {
        void BuildUp(object instance);
    }

    private class TypeCfg<T> : IBuildUp, IConfigureType<T> where T : class
    {
        private readonly List<FieldInfo> _fields;
        private readonly Dictionary<string, Action<T>> _initializers;

        public TypeCfg()
        {
            _fields = typeof(T)
                .GetFields(BindingFlags.Instance | BindingFlags.Public)
                .Where(IsLazyField)
                .ToList();
            _initializers = _fields.ToDictionary(x => x.Name, BuildDefaultSetter);
        }

        public IConfigureType<T> SetProvider<FT>(string fieldName, Func<T,FT> provider) 
        {
            var pi = _fields.First(x => x.Name == fieldName);
            _initializers[fieldName] = BuildSetter<FT>(pi, provider);
            return this;
        }

        public IConfigureType<T> SetProvider<F,FT>(Expression<Func<T,F>> fieldExpression, Func<T,FT> provider) 
            where F : LazyField<FT>
        {
            return SetProvider((fieldExpression.Body as MemberExpression).Member.Name, provider);
        }

        public void BuildUp(object instance)
        {
            var typedInstance = (T)instance;
            foreach (var initializer in _initializers.Values)
                initializer(typedInstance);
        }

        private bool IsLazyField(FieldInfo fi)
        {
            return fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(LazyField<>);
        }

        private Action<T> BuildDefaultSetter(FieldInfo fi)
        {
            var itemType = fi.FieldType.GetGenericArguments()[0];
            var defValue = Activator.CreateInstance(typeof(LazyField<>).MakeGenericType(itemType));
            return (inst) => fi.SetValue(inst, defValue);
        }

        private Action<T> BuildSetter<FT>(FieldInfo fi, Func<T, FT> provider)
        {
            return (inst) => fi.SetValue(inst, new LazyField<FT>(new Lazy<FT>(() => provider(inst))));
        }
    }

    private static IBuildUp BuildInitializer(Type targetType)
    {
        return (IBuildUp)Activator.CreateInstance(typeof(TypeCfg<>).MakeGenericType(targetType));
    }
}