Как вы можете требовать конструктор без параметров для типов, реализующих интерфейс?

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

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

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

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

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

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

10 ответов


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

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

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

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

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

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

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

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

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

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

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}

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

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

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


Хуан

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

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

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

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

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


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

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

class Test: ITest<Test>
{
    //...
}

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

public interface IInterface
{
    void DoSomething();
}

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

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

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());
}

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


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

void RegisterType<T>() where T:ITest, new() {
}

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

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


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

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

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

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


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

interface FooFactory {
    Foo createInstance();
}

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


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