14

という名前のクラスがあるとしFooます。

クラスを変更することはできませんが、 typeFooという名前のプロパティでクラスを拡張したくありません。Barstring

また、次のようなクラスがたくさんあるFooので、「一般的な」ソリューションに興味があります。

を調べていますがExpandoObjectdynamic求めている結果が得られますが、使用せずに実行できるのではないかと思っていましたdynamic...

static void Main(string[] args)
{
    var foo = new Foo() { Thing = "this" };
    var fooplus = Merge(foo, new { Bar = " and that" });
    Console.Write(string.Concat(fooplus.Thing, fooplus.Bar));
    Console.ReadKey();
}

public class Foo
{
    public string Thing { get; set; }
}

public static dynamic Merge(object item1, object item2)
{
    if (item1 == null || item2 == null)
    return item1 ?? item2 ?? new ExpandoObject();

    dynamic expando = new ExpandoObject();
    var result = expando as IDictionary<string, object>;
    foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item1, null);
    }
    foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item2, null);
    }
    return result;
}
4

4 に答える 4

17

この問題は、Reflection.Emit とランタイム コード生成を使用することで比較的簡単に解決できます。

拡張したい次のクラスがあるとします。

public class Person
{
    public int Age { get; set; }
}

このクラスは人物を表し、その人物の年齢を表す Ageという名前のプロパティを含みます。

あなたの場合、文字列型のNameプロパティを追加して、人の名前を表すこともできます。

最も単純で合理的な解決策は、次のインターフェイスを定義することです。

public interface IPerson
{   
    string Name { get; set; }
    int Age { get; set; }
}

クラスを拡張するために使用されるこのインターフェイスには、現在のクラスに含まれるすべての古いプロパティと、追加する新しいプロパティが含まれている必要があります。この理由はすぐに明らかになります。

次のクラス定義を使用して、実行時に新しい型を作成することで実際にクラスを拡張できます。これにより、上記のインターフェイスから派生させることもできます。

class DynamicExtension<T>
{
    public K ExtendWith<K>()
    { 
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Module");
        var type = module.DefineType("Class", TypeAttributes.Public, typeof(T));

        type.AddInterfaceImplementation(typeof(K));

        foreach (var v in typeof(K).GetProperties())
        {
            var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
            var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
            var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
            var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });

            var getGenerator = getter.GetILGenerator();
            var setGenerator = setter.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Ldfld, field);
            getGenerator.Emit(OpCodes.Ret);

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            setGenerator.Emit(OpCodes.Stfld, field);
            setGenerator.Emit(OpCodes.Ret);

            property.SetGetMethod(getter);
            property.SetSetMethod(setter);

            type.DefineMethodOverride(getter, v.GetGetMethod());
            type.DefineMethodOverride(setter, v.GetSetMethod());
        }

        return (K)Activator.CreateInstance(type.CreateType());
    }
}

このクラスを実際に使用するには、次のコード行を実行するだけです。

class Program
{
    static void Main(string[] args)
    {
        var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();

        extended.Age = 25;
        extended.Name = "Billy";

        Console.WriteLine(extended.Name + " is " + extended.Age);

        Console.Read();
    }
}

新しく作成したクラスを拡張するためにインターフェイスを使用した理由は、そのプロパティにタイプ セーフな方法でアクセスできるようにするためであることがわかります。単純にオブジェクト タイプを返すと、Reflection によってそのプロパティにアクセスする必要があります。

編集

次の変更されたバージョンでは、インターフェイス内にある複雑な型をインスタンス化し、他の単純なものを実装できるようになりました。

Person クラスの定義は同じままですが、IPerson インターフェイスは次のようになりました。

public interface IPerson
{
    string Name { get; set; }

    Person Person { get; set; }
}

DynamicExtension クラスの定義は、次のように変更されます。

class DynamicExtension<T>
{
    public T Extend()
    {
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Module");
        var type = module.DefineType("Class", TypeAttributes.Public);

        type.AddInterfaceImplementation(typeof(T));

        foreach (var v in typeof(T).GetProperties())
        {
            var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
            var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
            var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
            var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });

            var getGenerator = getter.GetILGenerator();
            var setGenerator = setter.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Ldfld, field);
            getGenerator.Emit(OpCodes.Ret);

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            setGenerator.Emit(OpCodes.Stfld, field);
            setGenerator.Emit(OpCodes.Ret);

            property.SetGetMethod(getter);
            property.SetSetMethod(setter);

            type.DefineMethodOverride(getter, v.GetGetMethod());
            type.DefineMethodOverride(setter, v.GetSetMethod());
        }

        var instance = (T)Activator.CreateInstance(type.CreateType());

        foreach (var v in typeof(T).GetProperties().Where(x => x.PropertyType.GetConstructor(new Type[0]) != null))
        {
            instance.GetType()
                    .GetProperty(v.Name)
                    .SetValue(instance, Activator.CreateInstance(v.PropertyType), null);
        }

        return instance;
    }
}

次のコード行を実行するだけで、適切な値をすべて取得できます。

class Program
{
    static void Main(string[] args)
    {
        var extended = new DynamicExtension<IPerson>().Extend();

        extended.Person.Age = 25;
        extended.Name = "Billy";

        Console.WriteLine(extended.Name + " is " + extended.Person.Age);

        Console.Read();
    }
}
于 2013-11-01T18:15:02.090 に答える
3

私のコメントが非常に冗長になったので、新しい回答を追加すると思いました。この答えは完全にマリオの仕事と考えであり、私が伝えようとしていることを例示するために私のマイナーな追加のみがあります.

これを非常にうまく機能させるには、mario の例にいくつかのマイナーな変更があります。つまり、クラス全体を複製するのではなく、既存のプロパティがクラス オブジェクトとして追加されるという事実を変更するだけです。とにかく、これがどのように見えるかです(修正されたセクションのみが追加され、他のすべてはマリオの回答のままです):

public class Person
{
    public int Age { get; set; }
    public string FaveQuotation { get; set; }
}

インターフェイスについては、プロパティをコピーするのではなくIPerson、実際のクラスを追加します。Person

public interface IPerson
{
    // extended property(s)
    string Name { get; set; }
    // base class to extend - tho we should try to overcome using this
    Person Person { get; set; }
}

これは、次の更新された使用法に変換されます。

static void Main(string[] args)
{
    var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();

    var pocoPerson = new Person
    {
        Age = 25,
        FaveQuotation = "2B or not 2B, that is the pencil"
    };

    // the end game would be to be able to say: 
    // extended.Age = 25; extended.FaveQuotation = "etc";
    // rather than using the Person object along the lines below
    extended.Person = pocoPerson;
    extended.Name = "Billy";

    Console.WriteLine(extended.Name + " is " + extended.Person.Age 
        + " loves to say: '" + extended.Person.FaveQuotation + "'");

    Console.ReadKey();
}

Personこれが元のOPに役立つことを願っています。メソッドでクラスを新しいプロパティと同じレベルにフラット化する必要があるかどうかについて、陪審はまだ出ていません!! したがって、実際には、この行を使用するとnew DynamicExtension<Person>().ExtendWith<IPerson>();、完全に拡張された新しいオブジェクト (インテリジェンスが含まれている必要があります) を返す必要があります。タフなコール - うーん...

于 2013-11-05T23:21:30.900 に答える
2

クラス定義にアクセスできない場合、最善の方法は、ターゲット クラスから派生したクラスを作成することです。オリジナルでない限りSealed

于 2013-11-01T17:00:03.300 に答える