Как получить доступ к свойствам производного класса из экземпляра базового класса

#c# #design-patterns #reflection

#c# #шаблоны проектирования #отражение

Вопрос:

У меня есть следующий код, к которому я не могу получить доступ или изменить:

 class A{
}

class B:A{
    string name;
    int age;
    string desc;
}

class C:A{
    string name;
    int age;
    string desc;
}
  

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

 void myFunc(B b){
    b.name = "some_name";
    b.age = 27;
    b.desc = "some_desc";
}

void myFunc(C c){
    c.name = "some_name";
    c.age = 27;
    c.desc = "some_desc";
}
  

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

Комментарии:

1. Базовый класс ничего не знает о своих дочерних элементах.

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

3. «У меня есть следующий код, к которому я не могу получить доступ или изменить »

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

5. На самом деле вы ничего не можете сделать. Вы даже не можете написать функции, которые вы задаете в качестве примера, поскольку все свойства являются ЧАСТНЫМИ.

Ответ №1:

Мы можем найти несколько способов сделать это. Например, вы можете использовать метод расширения для типа: A и использовать отражение для задания в нем полей.

Вот так:

 public static class ClassExtensions
{
    public static void SetProperties(this A a, string name, int age, string desc)
    {
        Type type = a.GetType();
        type.GetField("name")?.SetValue(a, name);
        type.GetField("age")?.SetValue(a, age);
        type.GetField("desc")?.SetValue(a, desc);
    }
}
  

Вот как это работает:https://dotnetfiddle.net/9uR3bA

Или просто используйте dynamic, например:

 public static void Main(string[] args)
{
    var b = new B();
    SetValues(b, "yes", 5, "ok");

    var c = new C();
    SetValues(c, "no", 10, "not ok");
}

public static void SetValues(dynamic theClass, string name, int age, string desc)
{
    theClass.name = name;
    theClass.age = age;
    theClass.desc = desc;
}
  

Примечание: Я предполагаю, что поля или свойства будут общедоступными, а не частными.

Комментарии:

1. Упражнение: что произойдет, если кто-то вызовет эти методы с помощью class D : A {} ? Обратите внимание, что D также наследуется A , но не определяет никаких свойств.

2. @MarkSeemann Конечно, эти решения не сделают все идеальным. Я тоже этого не предлагал. Потому что основа того, с чем мы работаем, нарушена. Но вызов like b.SetProperties("yes", 5, "ok") выглядит наиболее естественным в коде. Но в любом случае я добавил Null-conditional, чтобы он не зависал. Это означает, что если вы передадите D , он ничего не сделает сейчас.

Ответ №2:

Это не очень хороший дизайн. Если вы можете каким-либо образом изменить это, настоятельно подумайте об этом, но если вы не можете, вот что вы можете сделать.

Для этого вам не нужно отражение. Понижение выполнит эту работу:

 void MyMethod(A a)
{
    if (a is B b)
    {
        b.name = "some_name";
        b.age = 27;
        b.desc = "some_desc";        
    }

    if (a is C c)
    {
        c.name = "some_name";
        c.age = 27;
        c.desc = "some_desc";
    }
}
  

Поскольку A это незапечатанный базовый класс, могут существовать другие производные классы ( class D : A и т.д.), Но поскольку MyMethod ничего не возвращает, передача любого другого подтипа в качестве аргумента просто не будет операцией.

Комментарии:

1. @Shay сказал, что классов может быть много, поэтому, вероятно, этот подход приведет к гигантскому методу отображения. На этом этапе не было бы лучше иметь какой-нибудь MappingProvider и зарегистрировать отдельные функции сопоставления в корневом каталоге композиции? (кстати, привет, Марк, большой поклонник здесь)

2. @DavidGuida Я признаю, что пропустил это предложение 😳 Очевидно, что будет какой-то порог, при котором понижение нецелесообразно, и где отражение является лучшим вариантом. Преимущество downcasting заключается в том, что оно по-прежнему обеспечивает безопасность во время компиляции, которую вы полностью отбрасываете за борт с отражением, поэтому, где именно находится этот переломный момент, зависит (как обычно) от обстоятельств.

3. о, конечно! Для меня отражение никогда не было вариантом. Я предлагал создать определенные классы сопоставления для каждого случая (или даже Func<>), зарегистрировать их в корневом каталоге композиции, а затем вызвать их через фабрику сопоставления, зарегистрированную как singleton. Тогда, если вы хотите использовать отражение в коде mapper, помогите себе: D

Ответ №3:

Просто любопытно: почему не использовать Automapper ? Он использует convention-based подход, и когда этого недостаточно, вы можете сами написать код сопоставления и внедрить его.