Как "клонировать" объект в объект подкласса?
У меня есть класс A
и класс B
, который наследует класс A
и расширяет его еще несколькими полями.
Имея объект A
типа A
, как я могу создать объект B
типа B
, который содержит все данные, содержащиеся в объекте A
?
Я пробовал a.MemberwiseClone()
, но это только дает мне другой тип A
. И я не могу отличить A
от B
, так как отношение наследования допускает только противоположный отбор.
Каков правильный способ сделать это?
Ответы
Ответ 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";
Пожалуйста, не уменьшайте меня, потому что вы думаете, что это глупо! Я столкнулся с множеством программистов, которые настолько сосредоточены на своем коде, что не понимают, что более простое решение смотрит на них в лицо.:)