Как вам может потребоваться конструктор без параметров для типов, реализующих интерфейс? - PullRequest
10 голосов
/ 26 августа 2008

Есть ли способ?

Мне нужны все типы, которые реализуют определенный интерфейс, чтобы иметь конструктор без параметров, это можно сделать?

Я разрабатываю базовый код для других разработчиков в моей компании для использования в конкретном проекте.

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

Интерфейс будет внутренним для сборки

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

Ответы [ 10 ]

21 голосов
/ 26 августа 2008

Не для того, чтобы быть слишком тупым, но вы неправильно поняли назначение интерфейсов.

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

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

5 голосов
/ 26 августа 2008

Хуан Мануэль сказал:

это одна из причин, по которой я не понимаю, почему она не может быть частью контракта в интерфейсе

Это косвенный механизм. Универсальный позволяет вам «обманывать» и отправлять информацию о типе вместе с интерфейсом. Важно помнить, что ограничение не связано с интерфейсом, с которым вы работаете напрямую. Это не ограничение для самого интерфейса, а для некоторого другого типа, который будет «ездить» по интерфейсу. Боюсь, это лучшее объяснение, которое я могу предложить.

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

public class Something : ITest<String>
{
  private Something() { }
}

Что-то происходит от ITest , но не реализует конструктор без параметров. Он скомпилируется нормально, потому что String реализует конструктор без параметров. Опять же, ограничение на T, и, следовательно, String, а не ITest или Something. Поскольку ограничение на T выполнено, оно будет скомпилировано. Но это не удастся во время выполнения.

Чтобы предотвратить некоторые случаи этой проблемы, вам нужно добавить еще одно ограничение для T, как показано ниже:

public interface ITest<T>
  where T : ITest<T>, new()
{
}

Обратите внимание на новое ограничение: T: ITest . Это ограничение указывает, что то, что вы передаете в параметр аргумента ITest , должно также получить из ITest .

Даже в этом случае это не предотвратит всех случаев отверстия. Код ниже будет хорошо скомпилирован, потому что A имеет конструктор без параметров. Но поскольку конструктор B без параметров является закрытым, создание экземпляра B с вашим процессом завершится неудачей во время выполнения.

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}
5 голосов
/ 26 августа 2008

Вы можете использовать ограничение параметра типа

interface ITest<T> where T: new()
{
    //...
}

class Test: ITest<Test>
{
    //...
}
5 голосов
/ 26 августа 2008

Juan

К сожалению, нет способа обойти это на строго типизированном языке. Вы не сможете гарантировать, что во время компиляции экземпляры классов смогут быть созданы вашим кодом на основе Activator.

(изд: удалено ошибочное альтернативное решение)

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

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

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

4 голосов
/ 16 сентября 2008

Итак, вам нужна вещь , которая может создавать экземпляры неизвестного типа, реализующие интерфейс. У вас есть три основных варианта: объект фабрики, объект типа или делегат. Вот данные:

public interface IInterface
{
    void DoSomething();
}

public class Foo : IInterface
{
    public void DoSomething() { /* whatever */ }
}

Использование Type довольно уродливо, но имеет смысл в некоторых сценариях:

public IInterface CreateUsingType(Type thingThatCreates)
{
    ConstructorInfo constructor = thingThatCreates.GetConstructor(Type.EmptyTypes);
    return (IInterface)constructor.Invoke(new object[0]);
}

public void Test()
{
    IInterface thing = CreateUsingType(typeof(Foo));
}

Самая большая проблема в том, что во время компиляции вы не можете гарантировать, что Foo имеет конструктор по умолчанию. Кроме того, отражение немного медленное, если это происходит с кодом, критичным для производительности.

Наиболее распространенным решением является использование фабрики:

public interface IFactory
{
    IInterface Create();
}

public class Factory<T> where T : IInterface, new()
{
    public IInterface Create() { return new T(); }
}

public IInterface CreateUsingFactory(IFactory factory)
{
    return factory.Create();
}

public void Test()
{
    IInterface thing = CreateUsingFactory(new Factory<Foo>());
}

В вышесказанном IFactory - это то, что действительно имеет значение. Factory - это просто вспомогательный класс для классов, которые do предоставляют конструктор по умолчанию. Это самое простое и часто лучшее решение.

Третье, в настоящее время необычное, но, скорее всего, более распространенное, решение - это использование делегата:

public IInterface CreateUsingDelegate(Func<IInterface> createCallback)
{
    return createCallback();
}

public void Test()
{
    IInterface thing = CreateUsingDelegate(() => new Foo());
}

Преимущество здесь в том, что код короткий и простой, может работать с любым методом построения и (с замыканиями) позволяет легко передавать дополнительные данные, необходимые для построения объектов.

1 голос
/ 31 августа 2008

Вызовите метод RegisterType с типом и ограничьте его с помощью обобщений. Затем, вместо того, чтобы ходить по сборкам, чтобы найти разработчиков ITest, просто сохраните их и создайте оттуда.

void RegisterType<T>() where T:ITest, new() {
}
0 голосов
/ 26 августа 2008

Вам не нужен конструктор без параметров, чтобы активатор мог создать экземпляр вашего класса. Вы можете иметь параметризованный конструктор и передать все параметры из Активатора. Проверьте MSDN на этом .

0 голосов
/ 26 августа 2008

Нет, ты не можешь этого сделать. Может быть, для вашей ситуации будет полезен заводской интерфейс? Что-то вроде:

interface FooFactory {
    Foo createInstance();
}

Для каждой реализации Foo вы создаете экземпляр FooFactory, который знает, как его создать.

0 голосов
/ 26 августа 2008

Я хотел бы напомнить всем, что:

  1. Написание атрибутов в .NET легко
  2. Написание инструментов статического анализа в .NET, обеспечивающих соответствие стандартам компании, очень просто

Написание инструмента для захвата всех конкретных классов, которые реализуют определенный интерфейс / имеют атрибут, и проверка наличия у него конструктора без параметров занимает около 5 минут усилий по кодированию. Вы добавляете его на шаг после сборки, и теперь у вас есть структура для любого другого статического анализа, который вам нужно выполнить.

Язык, компилятор, IDE, ваш мозг - все это инструменты. Используйте их!

0 голосов
/ 26 августа 2008

Я так не думаю.

Вы также не можете использовать абстрактный класс для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...