Возвратите объект как интерфейс из универсального метода
У меня один интерфейс InterfaceBase
и некоторые интерфейсы, полученные из него Interface1, Interface2
, Далее у меня есть классы, которые реализуют InterfaceX
интерфейсы, а не базовый.
Теперь я новичок в дженериках, и поэтому в моей голове появилось много новых подходов:( . Я хочу создать фабрику (статический класс), где я называю что-то вроде
Interface1 concrete1 = Factory.Get<Interface1>();
Вот моя (примерная) реализация фабрики, которая не работает:
public static class Factory {
public static T Get<T>() where T: InterfaceBase{
Type type = typeof(T);
//return new Concrete1() as T; // type T cannot be used with the as
//return new Concrete1() as type; //type not found
//return new Concrete1(); // cannot implicitly convert
//return new Concrete1() as InterfaceBase; //cannot convert IBase to T
//return new Concrete1() as Interface1; //cannot convert Interface1 to T
}
}
Чего я хочу добиться - это скрыть классы (они являются обработчиками веб-сервисов) от остальной части приложения, чтобы легко их обмениваться. Я хотел использовать фабрику, так как классы будут одиночными, и они будут храниться в Словаре внутри фабрики, поэтому фабрика может распространять их по всему приложению через этот метод, но как интерфейсы. Может быть, я не правильно использую ограничения, делать что-то не так? мой подход плох? Может ли быть что-то лучше, может быть, вся архитектура должна быть переработана? диаграмма, чтобы лучше показать архитектуру. Завод не в этом
5 ответов
Methinks - это то, что вы ищете, это "инъекция зависимости бедного человека". Я полагаю, для этого вам следует использовать настоящий контейнер IoC, существует множество вариантов (Unity, Castle Windsor, Ninject...).
Но в любом случае, если вы настаиваете на том, чтобы делать это самостоятельно, следуйте рекомендациям @Sergey Kudriavtsev. Просто убедитесь, что для каждого интерфейса вы возвращаете соответствующий конкретный класс. Что-то вроде этого:
public interface InterfaceBase { }
public interface Interface1 : InterfaceBase { }
public interface InterfaceX : InterfaceBase { }
public class Concrete1 : Interface1 { }
public class ConcreteX : InterfaceX { }
public static class Factory
{
public static T Get<T>()
where T : InterfaceBase
{
if (typeof(Interface1).IsAssignableFrom(typeof(T)))
{
return (T)(InterfaceBase)new Concrete1();
}
// ...
else if (typeof(InterfaceX).IsAssignableFrom(typeof(T)))
{
return (T)(InterfaceBase)new ConcreteX();
}
throw new ArgumentException("Invalid type " + typeof(T).Name, "T"); // Avoids "not all code paths return a value".
}
}
И вы вызываете это, передавая интерфейсную ссылку на завод:
var instance = factory.Get<Interface1>();
Это должно работать:
return (T)(new Concrete1());
Также код для вызова метода фабрики должен быть таким:
Interface1 concrete1 = Factory.Get<Interface1>();
Чтобы ответить на одну часть вашего вопроса:
return new Concrete1() as T; // type T cannot be used with the as
Тип T
не может быть использован с as
так как T
не известно, чтобы быть ссылочным типом. Вы можете ограничить класс ссылочным типом с помощью where T : class [, ...]
ограничение. Это позволит вам использовать as
оператор, предполагая, конечно, что вам не нужно иметь возможность использовать этот метод с типами значений.
РЕДАКТИРОВАТЬ
Сказав это, я предпочитаю ответ Рсенны. Поскольку во время компиляции вы знаете, что прямое приведение будет работать, имеет смысл использовать прямое приведение, чемas
, Я также согласен с его рекомендацией исследовать "настоящие" контейнеры IoC, но я хотел бы добавить, что глубокое понимание обобщений будет очень полезно для вас, когда вы узнаете о них. Поскольку вы говорите, что вы новичок в дженериках, такое упражнение, вероятно, является хорошей идеей, поскольку оно поможет вам узнать об дженериках и даст вам лучшее понимание ценности, которую могут добавить контейнеры IoC.
РЕДАКТИРОВАТЬ 2
Я вижу еще одну проблему: вы ограничиваете T как InterfaceBase, а затем вы приводите Concrete1 к T. Не известно, что Concrete1 является производным от T! Что, конечно, почему вы используете as
бросать. Ответ таков: class
ограничение, и вы должны быть в порядке.
РЕДАКТИРОВАТЬ 3
Как указывает rsenna, вы также можете получить проверку типа во время выполнения с upcast и downcast:
return (T)(object)new Concrete1();
или же
return (T)(InterfaceBase)new Concrete1();
Интересно, как это сравнивается с точки зрения эффективности с
return new Concrete1() as T;
Сегодня я проверю, найду ли я для этого время.
Делать что-то вроде этого
public static class Factory {
public static T Get<T>() where T: InterfaceBase{
return (T) new Concrete1();
}
Не может быть напечатан безопасно. Вы не можете гарантировать, что метод будет вызван с T == Concrete1. T может быть ЛЮБОМ подтипом InterfaceBase, Concrete1 - это только один из этих подтипов и не обязательно тот же T, поэтому компилятор не позволит вам приводить к T, так же, как он не позволяет приводить к строке или любому другому другой не связанный тип.
Activator.CreateInstance () - это один из способов обработки этого: CreateInstance действительно гарантирует, что встроенный экземпляр имеет тип T, который является ожидаемым выходным значением метода.
Если вы знаете, что T является подтипом InterfaceBase (где T: InterfaceBase), тогда вы можете сделать тип возврата метода Get равным InterfaceBase:
public static InterfaceBase Get<T>() where T : InterfaceBase
{
return new Concrete1();
}
Этот метод может быть вызван с использованием подчиненного интерфейса InterfaceBase как T:
InterfaceBase ib = Factory.Get<Interface1>();
Если вы передадите что-либо кроме подчиненного интерфейса InterfaceBase как T, компилятор будет жаловаться.
Я думаю, что это лучший подход, но, как указывает @phoog, он работает только с конкретными классами для параметра типа T:
public static T Get<T>() where T : InterfaceBase
{
Type type = typeof(T);
if (t.IsAbstract || t.IsInterface)
{
throw new ArgumentException(@"Only non-abstract classes supported as T type parameter.");
}
return Activator.CreateInstance<T>();
}