Подтвердить что ты не робот

Как "клонировать" объект в объект подкласса?

У меня есть класс A и класс B, который наследует класс A и расширяет его еще несколькими полями.

Имея объект A типа A, как я могу создать объект B типа B, который содержит все данные, содержащиеся в объекте A?

Я пробовал a.MemberwiseClone(), но это только дает мне другой тип A. И я не могу отличить A от B, так как отношение наследования допускает только противоположный отбор.

Каков правильный способ сделать это?

4b9b3361

Ответ 1

Нет никакого способа сделать это автоматически встроенным в язык...

Один из вариантов - добавить конструктор в класс B, который принимает класс A в качестве аргумента.

Тогда вы могли бы сделать:

B newB = new B(myA);

В этом случае конструктор может просто скопировать соответствующие данные по мере необходимости.

Ответ 2

Я бы добавил конструктор копирования в A, а затем добавил новый конструктор в B, который берет экземпляр A и передает его базовому конструктору копии.

Ответ 3

Вы можете добиться этого, используя отражение.

Преимущество: Поддержание работоспособности. Нет необходимости в изменении конструктора-копии или аналогичного, добавления или удаления свойств.

Недостаток: производительность. Отражение происходит медленно. Мы все еще говорим миллисекунды на средних классах.

Здесь реализована реализация неполной копии на основе отражения, поддерживающая подкаталог-подкласс, используя методы расширения:

public static TOut GetShallowCopyByReflection<TOut>(this Object objIn) 
{
    Type inputType = objIn.GetType();
    Type outputType = typeof(TOut);
    if (!outputType.Equals(inputType) && !outputType.IsSubclassOf(inputType)) throw new ArgumentException(String.Format("{0} is not a sublcass of {1}", outputType, inputType));
    PropertyInfo[] properties = inputType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
    FieldInfo[] fields = inputType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
    TOut objOut = (TOut)Activator.CreateInstance(typeof(TOut));
    foreach (PropertyInfo property in properties)
    {
        try
        {
            property.SetValue(objIn, property.GetValue(objIn, null), null);
        }
        catch (ArgumentException) { } // For Get-only-properties
    }
    foreach (FieldInfo field in fields)
    {
        field.SetValue(objOut, field.GetValue(objIn));
    }
    return objOut;
}

Этот метод скопирует все свойства - частные и общедоступные, а также все поля. Свойства копируются по ссылке, делая ее мелкой копией.

Единичные тесты:

[TestClass]
public class ExtensionTests {
    [TestMethod]
    public void GetShallowCloneByReflection_PropsAndFields()
    {
        var uri = new Uri("http://www.stackoverflow.com");
        var source = new TestClassParent();
        source.SomePublicString = "Pu";
        source.SomePrivateString = "Pr";
        source.SomeInternalString = "I";
        source.SomeIntField = 6;
        source.SomeList = new List<Uri>() { uri };

        var dest = source.GetShallowCopyByReflection<TestClassChild>();
        Assert.AreEqual("Pu", dest.SomePublicString);
        Assert.AreEqual("Pr", dest.SomePrivateString);
        Assert.AreEqual("I", dest.SomeInternalString);
        Assert.AreEqual(6, dest.SomeIntField);
        Assert.AreSame(source.SomeList, dest.SomeList);
        Assert.AreSame(uri, dest.SomeList[0]);            
    }
}

internal class TestClassParent
{
    public String SomePublicString { get; set; }
    internal String SomeInternalString { get; set; }
    internal String SomePrivateString { get; set; }
    public String SomeGetOnlyString { get { return "Get"; } }
    internal List<Uri> SomeList { get; set; }
    internal int SomeIntField;
}

internal class TestClassChild : TestClassParent {}

Ответ 4

Использование Factory Шаблон метода:

    private abstract class A
    {
        public int A1 { get; set; }

        public abstract A CreateInstance();

        public virtual A Clone()
        {
            var instance = CreateInstance();
            instance.A1 = A1;
            return instance;
        }
    }

    private class B : A
    {
        public int A3 { get; set; }

        public override A CreateInstance()
        {
            return new B();
        }

        public override A Clone()
        {
            var result = (B) base.Clone();
            result.A3 = A3;
            return result;
        }
    }

    private static void Main(string[] args)
    {
        var b = new B() { A1 = 1, A3 = 2 };

        var c = b.Clone();
    }

Ответ 5

Создайте ctor в B, который позволяет передавать объект типа A, затем скопируйте поля A и установите соответствующие поля B.

Ответ 6

Вы можете сделать метод Convert для класса B, который принимает базовый класс.

public ClassB Convert(ClassA a)
{
   ClassB b = new ClassB();
   // Set the properties
   return b;
}

У вас также может быть конструктор для класса B в объекте ClassA.

Ответ 7

Нет, вы не можете этого сделать. Один из способов добиться этого - добавить конструктор класса B, который принимает параметр типа B, и добавлять данные вручную.

Итак, у вас может быть что-то вроде этого:

public class B
{
  public B(A a)
  {
    this.Foo = a.foo;
    this.Bar = a.bar;
    // add some B-specific data here
  }
}

Ответ 8

В базовом классе добавьте виртуальный метод CreateObject ниже...

    public virtual T CreateObject<T>()
    {
        if (typeof(T).IsSubclassOf(this.GetType()))
        {
            throw new InvalidCastException(this.GetType().ToString() + " does not inherit from " + typeof(T).ToString());
        }

        T ret = System.Activator.CreateInstance<T>();

        PropertyInfo[] propTo = ret.GetType().GetProperties();
        PropertyInfo[] propFrom = this.GetType().GetProperties();

        // for each property check whether this data item has an equivalent property
        // and copy over the property values as neccesary.
        foreach (PropertyInfo propT in propTo)
        {
            foreach (PropertyInfo propF in propFrom)
            {
                if (propT.Name == propF.Name)
                {
                    propF.SetValue(ret,propF.GetValue(this));
                    break;
                }
            }
        }

        return ret;
    }

затем скажите, что вы хотите создать объект подкласса реального жизни из суперкласса, просто вызовите

this.CreateObject<subclass>();

Это должно сделать это!

Ответ 9

Пока никто не предлагал это (и это не будет работать для всех, по общему признанию), следует сказать, что если у вас есть возможность создать объект b из get-go, сделайте это вместо создания объекта, а затем скопируйте объект b. Например, представьте, что вы находитесь в той же функции и имеете этот код:

var a = new A();
a.prop1 = "value";
a.prop2 = "value";
...
// now you need a B object instance...
var b = new B();
// now you need to copy a into b...

Вместо того, чтобы беспокоиться об этом последнем прокомментированном шаге, просто начните с b и установите значения:

var b = new B();
b.prop1 = "value";
b.prop2 = "value";

Пожалуйста, не уменьшайте меня, потому что вы думаете, что это глупо! Я столкнулся с множеством программистов, которые настолько сосредоточены на своем коде, что не понимают, что более простое решение смотрит на них в лицо.:)