В C# генерики - Как избежать избыточных метод?
Позвольте'ы предположим, у меня есть два класса, которые выглядят так (первый блок кода и общие проблемы, связанные с C#):
class A
{
public int IntProperty { get; set; }
}
class B
{
public int IntProperty { get; set; }
}
Эти классы не могут быть изменены в любую сторону (они входят в состав 3-го партийного съезда). Поэтому, я не могу заставить их реализовать тот же интерфейс, или унаследовать один и тот же класс, что бы потом содержать IntProperty.
Я хочу, чтобы применить какую-то логику в IntProperty собственностью обоих классов, и в C++ я могу использовать класс шаблона, чтобы сделать это достаточно легко:
template <class T>
class LogicToBeApplied
{
public:
void T CreateElement();
};
template <class T>
T LogicToBeApplied<T>::CreateElement()
{
T retVal;
retVal.IntProperty = 50;
return retVal;
}
И тогда я мог бы сделать что-то вроде этого:
LogicToBeApplied<ClassA> classALogic;
LogicToBeApplied<ClassB> classBLogic;
ClassA classAElement = classALogic.CreateElement();
ClassB classBElement = classBLogic.CreateElement();
Таким образом, я мог бы создать один универсальный класс фабрики, который будет работать для обоих classa и classb.
Однако, в C#, мне нужно написать два класса с двумя различными, где
п. Хотя код для логика точно такая же:
public class LogicAToBeApplied<T> where T : ClassA, new()
{
public T CreateElement()
{
T retVal = new T();
retVal.IntProperty = 50;
return retVal;
}
}
public class LogicBToBeApplied<T> where T : ClassB, new()
{
public T CreateElement()
{
T retVal = new T();
retVal.IntProperty = 50;
return retVal;
}
}
Я знаю, что если я хочу иметь разные классы В, где
положение, они должны быть связаны, т. е. наследование одного класса, если я хочу применить тот же код в том смысле, что я описал выше. Это просто, что это очень раздражает, чтобы иметь две абсолютно идентичные методы. Я также не хотите использовать отражение из-за проблем с производительностью.
Кто-нибудь может предложить подход, где это может быть написано в более элегантной моды?
Добавить прокси-интерфейс (иногда называют adapter, иногда с тонкими различиями), реализации LogicToBeApplied с точки зрения прокси-сервер, то добавить способ, чтобы создать экземпляр этого прокси из две лямбды: одна за собственность вам и на.
Теперь, когда вам нужно, чтобы передать в IProxy но есть экземпляр класса третьей стороны, вы можете просто пройти в несколько лямбд:
Кроме того, вы можете писать простые помощники для построения LamdaProxy экземпляры из случаев А или Б. Они даже могут быть методы расширения, чтобы дать вам и "свободно" в стиле:
И сейчас строительство прокси выглядит так:
Как на вашем заводе, я'd см. Если вы можете выполнить рефакторинг его в том, что "Главное" фабричный метод, который принимает IProxy и выполняет всю логику на нем и другие методы, которые как раз проходят в новой().Индикатора()
или
новый B().Индикатора()`:Там's никакой способ сделать эквивалент кода C++ в C#, потому что шаблоны C++ полагаться на структурных ввод текста. Пока два класса имеют одинаковое имя метода и подпись, в C++ можно назвать универсальной, что способ на них обоих. В C# номинального набрав - имя класса или интерфейса является частью его типа. Поэтому классы " А " и " Б " нельзя обращаться так же, в любом качестве, если явный "это" Мои отношения определяется путем наследования или реализации интерфейса.
Если стандартного внедрения этих методов в классе-это слишком много, вы можете написать функцию, которая принимает объект и задумчиво строит
LambdaProxy
, глядя на конкретное наименование имущества:Это не плачевно, когда данный объект неправильного типа; отражение неотъемлемо связан с возможностью сбоев с системе# тип не может предотвратить. К счастью, вы можете избежать отражения до тех пор, пока бремя содержания помощников становится слишком большим, потому что вы'повторно не требуется менять интерфейс IProxy или реализации LambdaProxy чтобы добавить светоотражающие сахара.
Часть этой работы заключается в том, что
LambdaProxy
есть "максимально универсального и"; он может адаптировать любое значение, которое реализуется в "дух" из IProxy договора за выполнение LambdaProxy определяется с учетом функций GET и set. Она работает даже если классы имеют различные имена для свойства, или различных типов, которые толково и спокойно представимыми как `инт, или если там's некоторый способ сопоставить понятие, что "собственность" должна представлять для любой другой функции класса. На косвенность предоставленной функции дает вам максимальную гибкость.Вот план, как использовать адаптеры без наследования от A и/или B, с возможностью использования их для существующих объектов а и Б:
Я, как правило, предпочитают этот вид объектный адаптер через прокси-класс, они избегают некрасивых проблем, вы можете столкнуться с наследованием. Например, это решение будет работать, даже если A и B-замкнутые классы.
Можно адаптировать
класса
ито
через общий интерфейс. Таким образом, ваш код вLogicAToBeApplied
остается неизменным. Не сильно отличается от того, что хоть у вас есть.Версию C++ работает только потому, что ее использование шаблонов “статическая типизация дак” – все будет компилироваться, если этот тип обеспечивает правильные имена. Это больше похоже на макро системы. Система обобщений в C# и других языках, работает очень по-разному.
devnull's и Док Браун's отвечает показать, как картина адаптер может быть использован, чтобы сохранить общий алгоритм, и по-прежнему работать на произвольных типов ... с парой ограничений. В частности, вы'вновь теперь создать другой тип, чем вы на самом деле хотите.
С небольшой хитростью, его можно использовать точно указанный тип без каких-либо изменений. Однако, теперь нам нужно извлечь все контакты с целевой тип в отдельный интерфейс. Вот эти взаимодействия работ и присвоение имущества:
В интерпретации ООП, это будет пример шаблон стратегии, хотя и смешанное с дженериков.
Тогда мы можем переписать свою логику, чтобы использовать эти взаимодействия:
Определения взаимодействия будет выглядеть следующим образом:
Большим недостатком этого подхода является то, что программист должен написать и передать экземпляр взаимодействие при вызове логики. Это довольно похожие на адаптер-шаблон на основе решения, но является несколько более общим.
По моему опыту, это ближайший вы можете получить к шаблону функции в других языках. Подобные методы используются в Haskell, Scala и идти, и ржавчина для реализации интерфейсов вне определения типа. Однако в этих языках шагов компилятора и выбирает нужный экземпляр взаимодействия неявно, так что вы Дон'т видели дополнительным аргументом. Это также похожие на C#'s расширение методов, но не ограничивается статическими методами.
Если вы действительно хотите бросить предостережение в ветер, вы можете использовать и"динамический", чтобы заставить компилятор позаботиться обо всех гадости размышления для вас. Это приведет к ошибке во время выполнения, если вы передаете объект в SetSomeProperty, что не имеет свойства с именем SomeProperty.
Остальные ответы правильно определить проблему и находить действенные решения. C# не (как правило) поддержку "с утиной типизацией, то" ("Если это ходит, как утка...и"), так там's не способ, чтобы заставить ваши
класса
ито
, чтобы быть взаимозаменяемыми, если они не'т так устроена.Однако, если вы're уже готовы принять риск выполнения неисправностей, то там'ы более простой ответ, чем с помощью отражения.
В C# есть
динамический
ключевое слово, которое идеально подходит для таких ситуаций. Он сообщает компилятору "Я выиграл'т знаю, что типа это до времени (а может даже и не тогда), так что позвольте мне сделать все, чтобы ее".Используя это, вы можете создать именно ту функцию, которую вы хотите:
Обратите внимание на использование "статический" ключевое слово, а также. Что позволяет использовать это:
Нет большой картинке точки зрения производительности, используя "динамический", так есть разовый урон (и сложности) с помощью отражения. Первый раз, когда ваш код попадает в динамический вызов с определенным типом будет небольшая сумма накладных, но повторные вызовы будут так же быстро, как стандартный код. Тем не менее, вы ** получите `RuntimeBinderException если ты попытаешься пройти в то, что не'т имеют это свойство, и там's не хороший способ проверить, что будет впереди. Вы, возможно, захотите, чтобы специально обработать эту ошибку в полезной манере.
Вы можете использовать отражение, чтобы вытащить свойству по имени.
Очевидно, вы рискуете ошибки во время выполнения этого метода. Что C# - это попытка остановить ты делаешь.
Я где-то читал, что в будущих версиях языка C# позволяют передавать объекты в качестве интерфейса, что они Дон'т наследовать, а не матч. Что бы решить вашу проблему.
(Я'Лл попробовать и выкопать статьи)
Другой способ, хотя я'м не уверен, что это экономит вам любой код будет на подклассы A и B, а также наследовать интерфейс с IntProperty.
Я просто хотел использовать неявный оператор преобразования вместе с подходом делегат/лямбда Джека'ы ответ.
A
иB
являются, как предполагалось:Тогда легко получить хороший синтаксис с неявные пользовательские преобразования (методы продления или аналогичная требуется):
Иллюстрация использования:
В
инициализации
метод показывает, как можно работать с "адаптер", не заботясь о том, будет ли это " А " или " Б " или что-то другое. На призывы оинициализировать
способ показать, что нам не нужно никаких (видимых) литой или.AsProxy () или аналогичные для лечения бетонаA
илиB
в качествеадаптер
.Рассмотреть, если вы хотите бросить
исключение ArgumentNullException
в определенных пользователем преобразований, если переданный аргумент является пустой ссылкой, или нет.