Создание клонированной копии подкласса из базового класса - PullRequest
4 голосов
/ 26 января 2012

Рассмотрим этот сценарий:

public class Base
{
    public int i;
}

public class Sub : Base
{
    public void foo() { /* do stuff */}
}

И затем я хочу, учитывая экземпляр Base, получить клонированный экземпляр Sub (с i = 17 в данном случае), чтобы я мог вызвать foo в подклассе.

Base b = new Base { i=17 };
Sub s = CloneAndUpcast(b);
s.foo();

Однако, как я могу создать CloneAndUpcast?

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

Кто-нибудь с лучшими, аккуратными идеями?

PS. Сценарий, в котором я думаю об использовании этого, представляет собой набор «простых» классов в древовидной структуре (здесь нет циклических графов или подобных), и все классы являются простыми держателями значений. План состоит в том, чтобы иметь тупой слой, содержащий все значения, а затем аналогичный набор классов (подклассов), который фактически содержит некоторую бизнес-логику, о которой владельцы значений не должны знать. Вообще плохая практика да. Я думаю, что это работает в этом случае.

Ответы [ 5 ]

13 голосов
/ 29 сентября 2012

Вы можете использовать AutoMapper, чтобы избежать утомительной записи конструкторов копирования.

public class MyClass : MyBase
{
    public MyClass(MyBase source)
    {
        Mapper.Map(source, this);
    }
}

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

Mapper.CreateMap<MyBase, MyClass>();

Вы можете скачатьAutoMapper от https://github.com/AutoMapper/AutoMapper

7 голосов
/ 26 января 2012

Вот один способ (из многих возможностей), что вы могли бы сделать что-то, как вы просите. Я не уверен, что это очень красиво и может быть довольно уродливо для отладки, но я думаю, что это работает:

class BaseClass
{
    public int i { get; set; }

    public BaseClass Clone(BaseClass b)
    {
        BaseClass clone = new BaseClass();
        clone.i = b.i;
        return clone;
    }

}

class SubClass : BaseClass
{
    public int j { get; set; }

    public void foo() { Console.WriteLine("in SubClass with value of i = {0}", i.ToString()); }
}

class Program
{
    static void Main(string[] args)
    {
        BaseClass b1 = new BaseClass() { i = 17 };
        BaseClass b2 = new BaseClass() { i = 35 };

        SubClass sub1 = CloneAndUpcast<SubClass>(b1);
        SubClass sub2 = CloneAndUpcast<SubClass>(b2);

        sub1.foo();
        sub2.foo();
    }

    static T CloneAndUpcast<T>(BaseClass b) where T : BaseClass, new()
    {
        T clone = new T();

        var members = b.GetType().GetMembers(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
        for (int i = 0; i < members.Length; i++)
        {
            if (members[i].MemberType== MemberTypes.Property)
            {
                clone
                    .GetType()
                    .GetProperty(members[i].Name)
                    .SetValue(clone, b.GetType().GetProperty(members[i].Name).GetValue(b, null), null);
            }

        }
        return clone;
    }
}

В основном, как вы и предлагали, вы используете отражение, чтобы перебрать свойства объекта (я установил i и j в качестве открытых свойств) и соответственно установить значения в клонированном объекте. Ключ использует дженерики, чтобы сообщить CloneAndUpcast, с каким типом вы имеете дело. Как только вы это сделаете, это довольно просто.

Надеюсь, это поможет. Удачи!

3 голосов
/ 03 декабря 2014

Согласно «Банде четырех»: «Пользуйся композицией, а не наследством», и это идеальная причина для этого ...

Если у нас есть суперкласс, который выглядит следующим образом:

public class SuperClass : Person 

Суперкласс может легко декорировать класс Person, добавляя свойства, не найденные в классе Person. Но что произойдет, если украшения Superclass предназначены только для графического интерфейса? Например, значение bool, обозначающее «Выбрано». Мы все еще можем получить всех людей из БД в списке, но у нас возникают проблемы при попытке создать суперкласс и объединить результаты БД.

    foreach( var person in myPersonList){
    var sc = new SuperClass();
    sc.Selected = false;
    sc=person;
 }

Компилятор жалуется, потому что Суперкласс не Перс, а компилятор. Единственный способ заполнить свойства подкласса Person - это выполнить итерацию и установить каждый из них ... вот так.

    SuperClass.Name = Person.Name;
    SuperClass.Id = Person.ID;  

Довольно скучно. Но есть и лучший способ .... Не заставляйте суперкласс наследовать от Person

public class SuperClass{
  public Person ThisPerson {get;set;}
  public bool Selected {get;set;}
}

Это дает нам «сдерживание». Суперкласс теперь содержит класс Person.

Теперь мы можем сделать это:

foreach(var person in MyPersonList){
   var sc = new Superclass();
   sc.Selected = false;
   sc.Person = person;
}

Потребитель этого класса теперь должен квалифицировать свойства Суперкласса / Человека следующим образом ...

forach(var sc in MySuperClassList){
  var selected = sc.Selected;
  var name = sc.Person.Name;
}

Прелесть этого в том, что в будущем вы можете добавить любой другой контейнер, который вам нужен, и он НЕ повлияет на другие контейнеры. Вы также можете трансформировать суперкласс во все, что в нем содержится. Если каждый из содержащихся в нем классов становится интерфейсом, то это еще один шаг вперед.

1 голос
/ 26 января 2012

Клонирование - плохая практика, и ваш вопрос является причиной этого (клонирование подкласса). В общем, вместо этого вы должны просто использовать копирование, чтобы подкласс принимал родителя в качестве параметра.

публичная база () {}

публичная база (Base pSource) {}

public Sub () {}

public Sub (Base pSource, другие параметры ...) {}

public Sub (Sub pSource) {}

1 голос
/ 26 января 2012

Ну, поскольку b не является Sub, мы не можем "клонировать" его как единое целое.

Если Base имеет подходящую комбинацию конструктора и общедоступных свойств, чтобы позволить конструктору в Sub гарантировать, что его база будет иметь то же состояние, что и b, то мы могли бы использовать это.

Думаю, я бы обошел все это. Если все, что нас волнует, это то, что s имеет то же состояние в своей базе, что и b, и у него нет другого состояния, о котором мы собираемся заботиться (иначе мы должны были бы передать его в CloneAndUpcast метод), тогда нам вообще нужен s

Статический метод может занять Base, и мы можем просто использовать static public void foo(Base bc). Мы могли бы даже определить его как метод расширения static public void foo(this Base bc), а затем закодировать вызов как b.foo(). Единственное, что не позволит нам сделать это CloneAndUpcast(), - это доступ к защищенным членам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...