Возвратите объект как интерфейс из универсального метода

У меня один интерфейс 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>();
}
Другие вопросы по тегам