Сведения о вопросе

DED

13:34, 27th August, 2020

Теги

c#   .net   constructor   interface   oop    

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

Просмотров: 530   Ответов: 10

Есть ли какой-то выход?

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

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

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

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

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



  Сведения об ответе

piter

04:32, 28th August, 2020

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

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

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


  Сведения об ответе

P_S_S

21:07, 9th August, 2020

Хуан,

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

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

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

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

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


  Сведения об ответе

SKY

08:59, 12th August, 2020

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

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

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


  Сведения об ответе

DAAA

19:56, 23rd August, 2020

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

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

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

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

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

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

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

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

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

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

public class A : ITest<A>
{
}

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


  Сведения об ответе

ASER

03:12, 22nd August, 2020

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

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-это просто класс удобства для классов, которые предоставляют конструктор по умолчанию. Это самое простое и часто лучшее решение.

Третье решение currently-uncommon-but-likely-to-become-more-common-это использование делегата:

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

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

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


  Сведения об ответе

ASER

21:06, 1st October, 2020

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

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


  Сведения об ответе

LIZA

03:34, 17th August, 2020

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

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

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

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


  Сведения об ответе

Chhiki

07:35, 14th August, 2020

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

interface FooFactory {
    Foo createInstance();
}

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


  Сведения об ответе

padenie

21:06, 1st October, 2020

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


  Сведения об ответе

prince

08:04, 17th August, 2020

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться