5

3つの列挙値に応じて正しいサブクラスを返すファクトリメソッドがあります。そのための1つの方法は、スイッチのスイッチでスイッチを使用することです。明らかに、私はそのオプションがあまり好きではありません。

別のオプションは、C#で属性を使用することだと思いました。すべてのサブクラスにはその3つの列挙値を持つ属性があり、ファクトリでは、ファクトリにある列挙値に対応する同じ列挙値を持つクラスを取得するだけで済みます。

しかし、私は属性にまったく慣れておらず、Webで適切な解決策を見つけられませんでした。誰かが私にいくつかのヒントやコード行を教えてくれたら、本当にありがたいです!

4

5 に答える 5

4

まず、属性を宣言してクラスに追加します。

enum MyEnum
{
    Undefined,
    Set,
    Reset
}

class MyEnumAttribute : Attribute
{
    public MyEnumAttribute(MyEnum value)
    {
        Value = value;
    }

    public MyEnum Value { get; private set; }
}

[MyEnum(MyEnum.Reset)]
class ResetClass
{
}

[MyEnum(MyEnum.Set)]
class SetClass
{
}

[MyEnum(MyEnum.Undefined)]
class UndefinedClass
{
}

次に、このコードを使用して、列挙型と型を含む辞書を作成し、動的に型を作成できます。

//Populate a dictionary with Reflection
var dictionary = Assembly.GetExecutingAssembly().GetTypes().
    Select(t => new {t, Attribute = t.GetCustomAttribute(typeof (MyEnumAttribute))}).
    Where(e => e.Attribute != null).
    ToDictionary(e => (e.Attribute as MyEnumAttribute).Value, e => e.t);
//Assume that you dynamically want an instance of ResetClass
var wanted = MyEnum.Reset;
var instance = Activator.CreateInstance(dictionary[wanted]);
//The biggest downside is that instance will be of type object.
//My solution in this case was making each of those classes implement
//an interface or derive from a base class, so that their signatures
//would remain the same, but their behaviors would differ.

お気づきかもしれませんが、通話Activator.CreateInstanceはパフォーマンスが良くありません。したがって、パフォーマンスを少し向上させたい場合は、辞書を次のように変更できますDictionary<MyEnum,Func<object>>。値として型を追加する代わりに、各クラスのコンストラクターをラップしてオブジェクトとして返す関数を追加します。

編集:私はこのページConstructorFactoryから適応したクラスを追加しています。

static class ConstructorFactory
{
    static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
    {
        var paramsInfo = ctor.GetParameters();
        var param = Expression.Parameter(typeof(object[]), "args");
        var argsExp = new Expression[paramsInfo.Length];
        for (var i = 0; i < paramsInfo.Length; i++)
        {
            Expression index = Expression.Constant(i);
            var paramType = paramsInfo[i].ParameterType;
            Expression paramAccessorExp = Expression.ArrayIndex(param, index);
            Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType);
            argsExp[i] = paramCastExp;
        }
        var newExp = Expression.New(ctor, argsExp);
        var lambda = Expression.Lambda(typeof(ObjectActivator<T>), newExp, param);
        var compiled = (ObjectActivator<T>)lambda.Compile();
        return compiled;
    }

    public static Func<T> Create<T>(Type destType)
    {
        var ctor = destType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).First();
        Func<ConstructorInfo, object> activatorMethod = GetActivator<Type>;
        var method = typeof(ConstructorFactory).GetMethod(activatorMethod.Method.Name, BindingFlags.Static | BindingFlags.NonPublic);
        var generic = method.MakeGenericMethod(destType);
        dynamic activator = generic.Invoke(null, new object[] { ctor });
        return () => activator();
    }

    delegate T ObjectActivator<out T>(params object[] args);
}

の代わりに使用できますActivator.CreateInstance。結果がキャッシュされると、パフォーマンスが向上します。

var dictionary = Assembly.GetExecutingAssembly().GetTypes().
    Select(t => new { t, Attribute = t.GetCustomAttribute(typeof(MyEnumAttribute)) }).
    Where(e => e.Attribute != null).
    ToDictionary(e => (e.Attribute as MyEnumAttribute).Value, 
                 e => ConstructorFactory.Create<object>(e.t));
var wanted = MyEnum.Reset;
var instance = dictionary[wanted]();
于 2013-01-03T13:11:12.357 に答える
2

この記事をご覧ください:カスタム属性の作成。次に、リフレクション(たとえば、GetCustomAttributes)を使用して、属性とその値を取得できます。

お役に立てれば

于 2013-01-03T13:07:47.887 に答える
1
[AttributeUsage(AttributeTargets.Class)]
    public class SampleClass : Attribute {
        public SampleClass() : base() { }
        public SampleClass(YourEnum attributeValue) : this() { MyAttributeProperty = attributeValue; }
        public YourEnum MyAttributeProperty { get; set; }
    }

    public enum YourEnum { Value1, Value2, Value3 }

    [SampleClass(YourEnum.Value1)]
    public class ExampleValue1Class { }

    public class LoadOnlyClassesWithEnumValue1 {
        public LoadOnlyClassesWithEnumValue1() {

            Type[] allTypes = Assembly.GetExecutingAssembly().GetExportedTypes();
            foreach (var type in allTypes) {
                if (type.GetCustomAttributes(typeof(SampleClass), false).Length > 0) {
                    SampleClass theAttribute = type.GetCustomAttributes(typeof(SampleClass), false).Single() as SampleClass;
                    // this type is using SampleClass - I use .Single() cause I don't expect multiple SampleClass attributes, change ths if you want
                    // specify true instead of false to get base class attributes as well - i.e. ExampleValue1Class inherits from something else which has a SampleClass attribute
                    switch (theAttribute.MyAttributeProperty) {
                        case YourEnum.Value1:
                            // Do whatever
                            break;
                        case YourEnum.Value2:
                            // you want
                            break;
                        case YourEnum.Value3:
                        default:
                            // in your switch here
                            // You'll find the ExampleValue1Class object should hit the Value1 switch
                            break;
                    }
                }
            }
        }
    }

このようにして、列挙型を属性のパラメーターとして指定できます。本質的に、これは非常にシンプルで軽量なDIコンテナです。StructureMapやNInjectのようなものを使用するために、もっと複雑なものをお勧めします。

于 2013-01-03T13:12:16.707 に答える
1

別の解決策は、依存性注入(DI)コンテナーを使用することです。たとえば、Unity DIを使用すると、次のことができます。

// Register a named type mapping
myContainer.RegisterType<IMyObject, MyRealObject1>(MyEnum.Value1.ToString());
myContainer.RegisterType<IMyObject, MyRealObject2>(MyEnum.Value2.ToString());
myContainer.RegisterType<IMyObject, MyRealObject3>(MyEnum.Value3.ToString());
// Following code will return a new instance of MyRealObject1
var mySubclass = myContainer.Resolve<IMyObject>(myEnum.Value1.ToString());

Unityの使用例: Microsoft Unity(依存性注入)デザインパターンの実装

もちろん、任意のDIコンテナー(Castle Windsor、StructureMap、Ninject。使用可能な.NET DIコンテナーのリスト)を使用できます。.NET依存性注入コンテナー(IOC)のリスト

于 2013-01-03T14:16:35.353 に答える
0

属性を使用して情報を保持することは可能ですが、最終的には決定プロセスを行う必要があり、それほど変わらない可能性があります。属性の複雑さが増しただけです。決定の性質は、既存の3つの列挙から、または属性から、決定を行うための情報をどこで取得するかに関係なく、同じままです。

3つの列挙を組み合わせる方法を探す方がより実り多いかもしれません。

列挙型は任意の整数型にすることができるため、ネストされた(および冗長な)スイッチを排除する最も簡単な方法は、列挙型を組み合わせることです。列挙がフラグ値のコレクションである場合、これは最も簡単です。つまり、列挙型の各値には、バイナリ文字列の1ビットの値が含まれます(最初のビットに1、2番目のビットに2、3番目のビットに4、8、16など)。

各列挙の値を結合できる場合は、選択プロセスが1つのswitchステートメントになります。これは、列挙値を連結、乗算、または加算することによって行うのが最適ですが、それらを結合する方法は列挙に依存し、詳細を知らなければ、より明確な方向性を提供することは困難です。

于 2013-01-03T13:17:29.757 に答える