3

我有很多具有以下属性的类:

class C1
{
    [PropName("Prop1")]
    public string A {get;set;}

    [PropName("Prop2")]
    public string B {get;set;}

    [PropName("Prop3")]
    public string C {get;set;}
} 

class C2
{
    [PropName("Prop1")]
    public string D {get;set;}

    [PropName("Prop2")]
    public string E {get;set;}

    [PropName("Prop3")]
    public string F {get;set;}
} 

该属性告诉实际属性是什么,但 C# 属性的名称并不总是匹配。在 C1 和 C2 的情况下,C1.A 与 C2.D 具有相同的属性。

这些类不是任何继承链的一部分,我无法控制它们,因此我无法更改它们。

“Prop1”,“Prop2”,...,“PropN”有一些常见的操作。在没有太多代码重复但仍使其可维护的情况下编写这些操作的最佳解决方案是什么?

解决方案 #1(if 语句 - 很多)

void OperationWithProp1(object o)
{
    string prop1;        

    C1 class1 = o as C1;
    if (class1 != null)
        prop1 = class1.A;

    C2 class2 = o as C2;
    if (class2 != null)
        prop1 = class2.D;

    // Do something with prop1
}

解决方案#2(重载 - 很多)

void OperationWithProp1(string prop1)
{
    // Do something with prop1
}

void RunOperationWithProp1(C1 class1)
{
    OperationWithProp1(class1.A);
}

void RunOperationWithProp1(C2 class2)
{
    OperationWithProp1(class2.D);
}

解决方案#3(反射)-我担心性能,因为这些操作中的每一个都会被调用数千次,并且有数百个操作

void OperationWithProp1(object o)
{
     // Pseudo code:
     // Get all properties from o that have the PropName attribute
     // Look if any attribute matches "Prop1"
     // Get the value of the property that matches
     // Do something with the value of the property
}

您会选择哪种解决方案,为什么?你有其他的模式吗?


编辑澄清:

很多类意味着几十个

很多属性意味着 30-40 个属性/类

4

4 回答 4

4

您可以创建一个包装类来公开您需要的属性,并包装实际和类的实例。一种方法是通过代表:C1C2

interface WithProperties {
   string A {get;set;}
   string B {get;set;}
   string C {get;set;}
}
class WrappedCX<T> : WithProperties {
    private readonly T wrapped;
    private readonly Func<T,string> getA;
    private readonly Action<T,string> setA;
    private readonly Func<T,string> getB;
    private readonly Action<T,string> setB;
    private readonly Func<T,string> getC;
    private readonly Action<T,string> setC;
    public WrappedCX(T obj, Func<T,string> getA, Action<T,string> setA, Func<T,string> getB, Action<T,string> setB, Func<T,string> getC, Action<T,string> setC) {
        wrapped = obj;
        this.getA = getA;
        this.setA = setA;
        this.getB = getB;
        this.setB = setB;
        this.getC = getC;
        this.setC = setC;
    }
    public string A {
        get {return getA(wrapped);}
        set {setA(wrapped, value);}
    }
    public string B {
        get {return getB(wrapped);}
        set {setB(wrapped, value);}
    }
    public string C {
        get {return getC(wrapped);}
        set {setC(wrapped, value);}
    }
}

现在您可以执行以下操作:

C1 c1 = new C1();
C2 c2 = new C2();
WithProperties w1 = new WrappedCX(c1, c => c.A, (c,v) => {c.A=v;}, c => c.B, (c,v) => {c.B=v;}, c => c.C, (c,v) => {c.C=v;});
WithProperties w2 = new WrappedCX(c2, c => c.D, (c,v) => {c.D=v;}, c => c.E, (c,v) => {c.E=v;}, c => c.F, (c,v) => {c.F=v;});

此时,w1w2都实现了通用WithProperties接口,因此您可以在不检查它们的类型的情况下使用它们。

为了花哨,将七参数构造函数替换为带单个obj参数的构造函数,通过反射获取其类,检查您定义的自定义属性的属性,并创建/编译对应于 getter 和 setter 的 LINQ 表达式A,B和的属性C。这将让您WrappedCX在调用中没有丑陋的 lambdas 的情况下构建您的。这里的权衡是现在 lambdas 将在运行时构建,因此缺少属性的可能编译错误将成为运行时异常。

于 2013-01-16T17:37:07.617 回答
3

您可以使用属性化的“PropName”名称动态生成访问正确成员的代理类。您还想在生成对它们的调用之前检测属性是否实际实现了 get/set。也可能是一种更复杂的方法来保证生成的代理的唯一类型名称......

有关用法,请参见 Main(),下面 main 是 OperationWithProp1() 的实现

(这里有很多代码)

public interface IC
{
    string Prop1 { get; set; }
    string Prop2 { get; set; }
    string Prop3 { get; set; }
}

public class C1
{
    [PropName("Prop1")]
    public string A { get; set; }

    [PropName("Prop2")]
    public string B { get; set; }

    [PropName("Prop3")]
    public string C { get; set; }
}

public class C2
{
    [PropName("Prop1")]
    public string D { get; set; }

    [PropName("Prop2")]
    public string E { get; set; }

    [PropName("Prop3")]
    public string F { get; set; }
}

public class ProxyBuilder
{
    private static readonly Dictionary<Tuple<Type, Type>, Type> _proxyClasses = new Dictionary<Tuple<Type, Type>, Type>();

    private static readonly AssemblyName _assemblyName = new AssemblyName("ProxyBuilderClasses");
    private static readonly AssemblyBuilder _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.RunAndSave);
    private static readonly ModuleBuilder _moduleBuilder = _assemblyBuilder.DefineDynamicModule(_assemblyName.Name, _assemblyName.Name + ".dll");

    public static void SaveProxyAssembly()
    {
        _assemblyBuilder.Save(_assemblyName.Name + ".dll");
    }

    public static Type GetProxyTypeForBackingType(Type proxyInterface, Type backingType)
    {
        var key = Tuple.Create(proxyInterface, backingType);

        Type returnType;
        if (_proxyClasses.TryGetValue(key, out returnType))
            return returnType;

        var typeBuilder = _moduleBuilder.DefineType(
            "ProxyClassProxies." + "Proxy_" + proxyInterface.Name + "_To_" + backingType.Name,
            TypeAttributes.Public | TypeAttributes.Sealed,
            typeof (Object),
            new[]
            {
                proxyInterface
            });

        //build backing object field
        var backingObjectField = typeBuilder.DefineField("_backingObject", backingType, FieldAttributes.Private);

        //build constructor
        var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] {backingType});
        var ctorIL = ctor.GetILGenerator();
        ctorIL.Emit(OpCodes.Ldarg_0);
        var ctorInfo = typeof (Object).GetConstructor(types: Type.EmptyTypes);
        ctorIL.Emit(OpCodes.Call, ctorInfo);
        ctorIL.Emit(OpCodes.Ldarg_0);
        ctorIL.Emit(OpCodes.Ldarg_1);
        ctorIL.Emit(OpCodes.Stfld, backingObjectField);
        ctorIL.Emit(OpCodes.Ret);

        foreach (var targetPropertyInfo in backingType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var propertyName = targetPropertyInfo.Name;
            var attributes = targetPropertyInfo.GetCustomAttributes(typeof (PropName), true);

            if (attributes.Length > 0 && attributes[0] != null)
                propertyName = (attributes[0] as PropName).Name;

            var propBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, targetPropertyInfo.PropertyType, null);

            const MethodAttributes getSetAttrs =
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.Virtual;

            //build get method
            var getBuilder = typeBuilder.DefineMethod(
                "get_" + propertyName,
                getSetAttrs,
                targetPropertyInfo.PropertyType,
                Type.EmptyTypes);

            var getIL = getBuilder.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, backingObjectField);
            getIL.EmitCall(OpCodes.Callvirt, targetPropertyInfo.GetGetMethod(), Type.EmptyTypes);
            getIL.Emit(OpCodes.Ret);
            propBuilder.SetGetMethod(getBuilder);

            //build set method
            var setBuilder = typeBuilder.DefineMethod(
                "set_" + propertyName,
                getSetAttrs,
                null,
                new[] {targetPropertyInfo.PropertyType});

            var setIL = setBuilder.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldfld, backingObjectField);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.EmitCall(OpCodes.Callvirt, targetPropertyInfo.GetSetMethod(), new[] {targetPropertyInfo.PropertyType});
            setIL.Emit(OpCodes.Ret);
            propBuilder.SetSetMethod(setBuilder);
        }
        returnType = typeBuilder.CreateType();
        _proxyClasses.Add(key, returnType);
        return returnType;
    }

    public static TIProxy CreateProxyObject<TIProxy>(object backingObject, out TIProxy outProxy) where TIProxy : class
    {
        var t = GetProxyTypeForBackingType(typeof (TIProxy), backingObject.GetType());
        outProxy = Activator.CreateInstance(t, backingObject) as TIProxy;
        return outProxy;
    }


    private static void Main(string[] args)
    {
        var c1 = new C1();
        IC c1Proxy;
        CreateProxyObject(c1, out c1Proxy);
        var c2 = new C2();
        IC c2Proxy;
        CreateProxyObject(c2, out c2Proxy);

        c1Proxy.Prop1 = "c1Prop1Value";
        Debug.Assert(c1.A.Equals("c1Prop1Value"));

        c2Proxy.Prop1 = "c2Prop1Value";
        Debug.Assert(c2.D.Equals("c2Prop1Value"));

        //so you can check it out in reflector
        SaveProxyAssembly();
    }

    private static void OperationWithProp1(object o)
    {
        IC proxy;
        CreateProxyObject(o, out proxy);

        string prop1 = proxy.Prop1;

        // Do something with prop1
    }
于 2013-01-16T19:40:46.270 回答
1

为了获得最佳性能,您应该为每个属性编写一对静态方法,格式如下:

[PropName("Prop1")]
static string Prop1Getter(thisType it) { return it.WhateverProperty; }
[PropName("Prop1")]
static string Prop1Setter(thisType it, string st) { it.WhateverProperty = st; }

我建议您然后使用反射来生成委托,并使用静态泛型类来缓存它们。实际上,您将拥有一个私有静态类PropertyAccessors<T>,其委托声明如下:

const int numProperties = 3;
public Func<T, string>[] Getters;
public Action<T, string>[] Setters;

然后静态构造函数将执行以下操作:

Getters = new Func<T, string>[numProperties];
Setters = new Action<T, string>[numProperties];
for (int i = 0; i< numProperties; i++)
{
  int ii = i;  // Important--ensure closure is inside loop
  Getters[ii] = (T it) => FindSetAndRunGetter(ii, it);
  Setters[ii] = (T it, string st) => FindSetAndRunSetter(ii, it, st);
}

FindSetAndRunGetter(ii,it)方法应该搜索一个合适的属性getter,并且——如果找到——设置Getters[ii]为指向合适的属性getter,运行一次,然后返回结果。 应该同样使用属性设置器,将其作为参数FindSetAndRunSetter(ii, it, st)运行一次。st

使用这种方法将结合使用反射的多功能性和“自动升级”(意味着在未来类中自动查找方法的能力),其速度与硬编码方法相当(如果不是更好的话)。一个烦恼是需要定义如上所述的静态方法。可能可以使用Reflection.Emit自动生成包含此类方法的静态类,但这超出了我的专业水平。

于 2013-01-16T17:55:19.957 回答
1

IMO,为了清晰/可维护性而使用重载。如果有很多重叠代码,请将其分解为单独的方法。

话虽如此,我假设您首先关心可维护性,因为您没有提到速度。

于 2013-01-16T17:28:31.143 回答