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

时间:2015-06-03 15:53:50

标签: c# .net design-patterns properties lazy-loading

我已经用我的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个属性Test1&amp; Test2将使用加载到其中的函数来返回值。

实施看起来有点如下:

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课程中添加新方法,我需要添加:

  1. private Lazy<object> _myNewMethod;
  2. public object MyNewMethod { get { return _myNewMethod.Value; } }
  3. 使用要在字典中查找的键更新构造器
  4. 而且,虽然这很简单,但我喜欢拥有1行插件(可能是某种形式的自定义对象),或者直接从字典中读取属性而无需在所有

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

    感谢!!!

3 个答案:

答案 0 :(得分:1)

查看图书馆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);
    }
}

答案 1 :(得分:1)

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

为了简化操作,减少在具体类中键入内容并使类型安全,我们引入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;
    }
}

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

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

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

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

请注意,这些字段是不可变的,在构造函数中初始化。现在,对于您的使用示例,以下代码段显示了&#34;新方式&#34;这样做:

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

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

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

答案 2 :(得分:0)

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

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