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

You can make a wrapper class exposing the properties that you need, and wrapping instances of the actual C1 and C2 classes. One way of doing it would be through delegates:

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

Now you can do something like this:

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

At this point, w1 and w2 are both implementing the common WithProperties interface, so you can use them without checking their type.

To get fancy, replace the seven-argument constructor with a constructor that takes a single obj parameter, obtain its class via reflection, check its properties for the custom attributes that you defined, and create/compile LINQ expressions corresponding to the getters and the setters of the properties A, B, and C. This would let you construct your WrappedCX without the ugly lambdas trailing in the call. The tradeoff here is that now the lambdas would be constructed at run time, so would-be compile errors on missing properties would become run-time exceptions.

于 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

IMO、明確性/保守性のためにオーバーロードを使用してください。重複するコードが多い場合は、別のメソッドに分割します。

そうは言っても、速度について言及していないので、最初に保守性に関心があると思います。

于 2013-01-16T17:28:31.143 に答える
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)メソッドは、適切なプロパティ ゲッターを検索し、見つかった場合はGetters[ii]、適切なプロパティ ゲッターを指すように設定し、それを 1 回実行して、結果を返す必要があります。 FindSetAndRunSetter(ii, it, st)プロパティセッターでも同様に実行し、パラメーターとして一度実行する必要がstあります。

このアプローチを使用すると、リフレクションを使用することの汎用性と「自動アップグレード」 (将来のクラスでメソッドを自動的に見つける機能を意味する) が組み合わされ、ハードコーディングされたアプローチに匹敵する (それ以上ではないにしても) 速度が得られます。厄介なのは、上記のように静的メソッドを定義する必要があることです。Reflection.Emitそのようなメソッドを含む静的クラスを自動生成するために使用することはおそらく可能ですが、それは私の専門知識のレベルを超えています.

于 2013-01-16T17:55:19.957 に答える