根据具有属性的属性复制派生类的实例

时间:2018-07-18 12:39:16

标签: c# inheritance reflection attributes

假设我有一个抽象基类,我想要一个CreateCopy方法:

public abstract class BaseClass
{
    ///base stuff

    public BaseClass CreateCopy() //or public object, if necessary   
    {
        //?????
    }
}

假定所有派生类都具有无参数构造函数,并且属性字段(可以被标记)带有某种属性:

public class DerivedClass : BaseClass
{
    [CopiableProperty]
    public string Property1 {get; private set;}

    [CopiableProperty]
    public int Property2 {get; private set;}

    //no need to copy
    public int Property3 {get; private set;}

    //parameterless constructor
    public DerivedClass() { }
}

这种结构是否可以编写CreateCopy()的主体,使我可以使用正确的CopiableProperty字段创建派生对象的新实例?


自然,我可以制作一个public abstract BaseClass CreateCopy()并强制每个派生类照顾自己的副本,但是由于派生类的大小和数量,这会带来过多的额外工作。

3 个答案:

答案 0 :(得分:4)

一种相当简单的方法可以使用泛型和反射:

public abstract class BaseClass
{
    // restrict to children of BaseClass
    public T CreateCopy<T>() where T: BaseClass, new()
    {
        var copy = new T();

        // get properties that you actually care about
        var properties = typeof(T).GetProperties()
            .Where(x => x.GetCustomAttribute<CopiablePropertyAttribute>() != null);

        foreach (var property in properties)
        {
            // set the value to the copy from the instance that called this method
            property.SetValue(copy, property.GetValue(this));
        }

        return copy;
    }
}

public class DerivedClass : BaseClass
{
    [CopiableProperty]
    public string Property1 { get; set; }

    [CopiableProperty]
    public int Property2 { get; set; }

    public int Property3 { get; set; }

    public override string ToString()
    {
        return $"{Property1} - {Property2} - {Property3}";
    }
}

static void Main(string[] args)
{
    var original = new DerivedClass
    {
        Property1 = "Hello",
        Property2 = 123,
        Property3 = 500
    };

    var copy = original.CreateCopy<DerivedClass>();

    Console.WriteLine(original);
    Console.WriteLine(copy);

    Console.ReadLine();
}

这将打印:

Hello - 123 - 500
Hello - 123 - 0

如果您不介意依赖性,则另一种方法是利用序列化库:

public abstract class BaseClass
{
    public BaseClass CreateCopy()
    {
        string serialized = JsonConvert.SerializeObject(this);

        var actualType = GetType();

        return JsonConvert.DeserializeObject(serialized, actualType) as BaseClass;
    }
}

public class DerivedClass : BaseClass
{
    public string Property1 { get; set; }

    public int Property2 { get; set; }

    [JsonIgnore]
    public int Property3 { get; set; }

    //parameterless constructor
    public DerivedClass() { }

    public override string ToString()
    {
        return $"{Property1} - {Property2} - {Property3}";
    }
}

答案 1 :(得分:2)

我的解决方案通过JSON.NET nuget包使用序列化/反序列化。

在基类中不需要方法,可以改用扩展方法(adapted from this answer):

using Newtonsoft.Json;

public static class ObjectExtensions
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        var clone = JsonConvert.DeserializeObject<T>(serialized);

        return clone;
    }
}

然后使用属性控制应复制或不复制哪些属性-例如:

using Newtonsoft.Json;

public class DerivedClass : BaseClass
{
    public string Property1 { get; set; }

    public int Property2 { get; set; }

    [JsonIgnore]
    public int Property3 { get; set; }
}

使用代码:

var obj1 = new DerivedClass
{
    Property1 = "Abc",
    Property2 = 999,
    Property3 = 123
};

DerivedClass clone = obj1.Clone();

结果-如您所见,Property3在克隆对象中具有默认值:

Clone results

答案 2 :(得分:1)

遍历类型中的所有属性,并使用GetCustomAttributes检查您的属性。查看代码:

public BaseClass CreateCopy()
{
    var type = GetType();
    var result = Activator.CreateInstance(type);

    foreach (var propertyInfo in type.GetProperties())
    {
        var skipThisProperty = !propertyInfo.GetCustomAttributes(
                typeof(CopiablePropertyAttribute), false)
            .Any();

        if (skipThisProperty)
            continue;

        var value = propertyInfo.GetValue(this, null);
        propertyInfo.SetValue(result, value, null);
    }

    return (BaseClass) result;
}

请注意nullGetValue中的SetValue参数。如果您的属性是索引器,则需要传递正确的值作为最后一个参数