#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
подход, и когда этого недостаточно, вы можете сами написать код сопоставления и внедрить его.