Как я могу реализовать статические методы в интерфейсе?

92

У меня есть сторонняя C ++ DLL, которую я вызываю из C #.

Методы статичны.

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

Модификатор static недействителен для этого элемента

MyMethod cannot be accessed with an instance reference; qualify it with a type name instead

Как я могу добиться этой абстракции?

Мой код выглядит так

private IInterfaceWithStaticMethods MyInterface;

public MyClass(IInterfaceWithStaticMethods myInterface)
{
  this.MyInterface = myInterface;
}

public void MyMethod()
{
  MyInterface.StaticMethod();
}
Джон
источник
3
Может быть, вы сможете сделать это с помощью методов расширения: stackoverflow.com/questions/1243921/…
hcb

Ответы:

47

Вы не можете определять статические члены в интерфейсе на C #. Интерфейс - это контракт для экземпляров .

Я бы порекомендовал создать интерфейс таким, какой вы есть сейчас, но без ключевого слова static. Затем создайте класс StaticIInterface, реализующий интерфейс и вызывающий статические методы C ++. Чтобы выполнить модульное тестирование, создайте другой класс FakeIInterface, который также реализует интерфейс, но делает то, что вам нужно для обработки ваших модульных тестов.

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

Давизоа
источник
64
-1 для того, чтобы сказать An interface is a contract, not an implementation.- это правда, но здесь совершенно неактуально ( non sequitur ), поскольку статический метод не является частью самой реализации - реализация, по определению, основана на данных , которые, в свою очередь, недоступны для статических членов. An interface type definition can define and implement static methods (see §8.4.3) since static methods are associated with the interface type itself rather than with any value of the type.- помните, что staticчлены обычно являются служебными методами .
2
Я понимаю и согласен с вашими утверждениями, и я считаю, что ваш комментарий также важен для контекста. Несмотря на то что. при разработке интерфейса следует думать о нем как о контракте, который подразумевает, что статические методы не применяются. Я подумал, что мне следует оставить это там, чтобы помочь некоторым людям понять назначение интерфейса. Считает ли сообщество, что его следует удалить?
Дэвизоа 02
1
Отчасти согласен, что An interface is a contract, not an implementationбесполезно, иногда немного помогает контекстуализация. И я полностью согласен с тем static method is not a part of implementation itself , что у статических методов есть реализация, они становятся частью реализации, только если используются в качестве реализации в реализации другого метода. Однако мой словарь основан на том, что выучил, насколько я знаю, терминология действительно меняется в зависимости от языка программирования. Статические методы не могут быть интерфейсами, потому что в любом случае может быть только одна реализация.
CoffeDeveloper
Представьте, что у меня есть IPersonконтракт, в котором указано, что GetCountryбудет указано имя страны происхождения человека ... FrenchPersonвсе сущности будут говорить «Франция» и GermanPersonвсе будут говорить «Германия», что также полезно, когда разные типы сущностей используют одну и ту же таблицу (данных), например MS Скажем Connection, Azure one Postи Commentхранятся в UsersAzureTable, поэтому у сущностей дерева есть общая информация, IUsersможет быть GetTableNameстатический метод ...
Серж,
@vaxquis - ИМХО, «это контракт» было бы актуально, если бы предложение было перефразировано: `Интерфейс - это контракт для экземпляров . Статические члены являются частью типа; это измененное предложение говорит (правильно), что они не имеют значения в контракте экземпляра. Поэтому я думаю, что проблема заключается в неточной формулировке, а не в непоследовательности.
ToolmakerSteve
112

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

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

public interface IMyInterface
{
    void MyMethod();
}

public class MyClass : IMyInterface
{
    static void MyMethod()
    {
    }

    void IMyInterface.MyMethod()
    {
        MyClass.MyMethod();
    }
}

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

Дэнни Варод
источник
18
Для тех, кто задается вопросом, зачем кому-то это нужно, это особенно полезно при написании модульных / интеграционных тестов для устаревшего кода, реализующего статические методы.
Dezzamondo
Этот метод очень хорошо подходит для реализации быстрого RESTful API, который должен сохранять данные, но не может использовать базу данных. Реализация работала только с объектами C # в памяти, поэтому не было места для хранения данных, но использование статического свойства устраняло потребность в базе данных в памяти с использованием EF Core или SQLite.
gware
19

Статические члены совершенно допустимы в CLR, но не в C #.

Вы можете реализовать некоторый клей в IL, чтобы связать детали реализации.

Не уверены, что компилятор C # позволяет их вызывать?

См .: 8.9.4 Определение типа интерфейса ECMA-335.

Типы интерфейсов обязательно неполные, поскольку они ничего не говорят о представлении значений типа интерфейса. По этой причине определение типа интерфейса не должно предоставлять определения полей для значений типа интерфейса (т. Е. Полей экземпляра), хотя оно может объявлять статические поля (см. §8.4.3).

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

леппи
источник
10
Для справки: CLS Rule 19: CLS-compliant interfaces shall not define static methods, nor shall they define fields.далее говорится, что CLS-совместимые потребители могут отклонить такие интерфейсы. Около года назад я пытался вызвать статический метод интерфейса, но компилятор C # не смог его скомпилировать.
Кристофер Карренс
В дополнение к примечанию @ChristopherCurrens о CLS: Common Language Specification (CLS) is a set of basic language features that .Net Languages needed.... When there is a situation to communicate Objects written in different .Net Complaint languages , those objects must expose the features that are common to all the languages. имеет смысл, что если CLS касается взаимодействия между разными языками .NET, а C # не допускает статических членов в интерфейсе, то CLS также запретит их, чтобы библиотеки в другие языки .NET можно вызывать из C #.
Саймон Тевси
17

Вы можете определить статические методы в C # 8, но вы должны объявить для него тело по умолчанию.

    public interface IMyInterface
    {
          static string GetHello() =>  "Default Hello from interface" ;
          static void WriteWorld() => Console.WriteLine("Writing World from interface");
    }

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

    public interface IMyInterface
    {
          static string GetHello() =>  throw new NotImplementedException() ;
          static void WriteWorld() => throw new NotImplementedException();
    }
АлиРеза
источник
Кажется, что статические члены в интерфейсах довольно бесполезны, потому что вы не можете получить к ним доступ через экземпляр интерфейса. По крайней мере, на C # 8.
Павел Сапехин
3
с точки зрения реализации интерфейса, ваше право. это бесполезно. но таким образом, по крайней мере, у вас будет реализованный метод для каждого класса, использующего этот интерфейс. (это своего рода дополнительная реализация для интерфейсов)
AliReza
5

Вы можете вызвать его с помощью отражения:

MyInterface.GetType().InvokeMember("StaticMethod", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, null);
Джон Кернер
источник
5
А если у вас нет экземпляра MyInterface, вы можете использовать typeOf (MyInterface) вместо myInterface.GetType ().
RenniePet
В то время это казалось хорошей идеей, и я могу продолжать делать это с помощью рефлексии, но одно небольшое предупреждение: это действительно становится более проблематичным, если программа запутывается так, что метод StaticMethod переименовывается.
RenniePet
1
@RenniePet: Вы можете частично справиться с переименованием StaticMethod, используя вместо этого nameof (StaticMethod). Это МОЖЕТ помочь с обфускатором, в зависимости от того, как он переименовывает. Если вы сделаете это таким образом, вы, по крайней мере, увидите ошибку времени компиляции.
Brent Rittenhouse
Отражение слишком крайнее для этого случая
Степан Иваненко
3

C # «Ten» будет разрешать статические члены в интерфейсах наряду с ролями. Это огромный шаг вперед, он также позволит перегрузить общий оператор без использования рефлексии. Вот пример фрагмента того, как это работает, используя классический пример моноида, который является просто жаргоном для выражения «что-то, что можно добавить». Взято непосредственно из Mads Torgersen: C # в будущее :

interface IMonoid<T>
{
    static T Zero { get; }
    static T operator +(T t1, T t2);
}

public static T AddAll<T>(T[] ts) where T : IMonoid<T>
{
    T result = T.Zero;
    foreach (T t in ts) { result += t; }
    return result;
}

role IntAddMonoid extends int : IMonoid<int>
{
    public static int Zero => 0;
}

IntAddMonoid[] values = new int[] {1, 2, 4, 8, 16, 32};
int sixtyThree = AddAll<IntAddMonoid>(values); // == 63

Дополнительные ресурсы:

Джереми Байтс: статические члены интерфейса C # 8

РЕДАКТИРОВАТЬ

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

Тамас Хегедус
источник
1

Что касается того, почему у вас не может быть статического метода в интерфейсе: почему C # не позволяет статическим методам реализовывать интерфейс?

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

т.е.

public static class MyStaticClass
{
    public static void MyStaticMethod()
    {...}
}

public interface IStaticWrapper
{
    void MyMethod();
}

public class MyClass : IStaticWrapper
{
    public void MyMethod()
    {
        MyStaticClass.MyStaticMethod();
    }
}
Джастин Пихони
источник
В чем преимущество использования интерфейса со статическим классом по сравнению с использованием только интерфейса?
Селен
1

C # 8 допускает использование статических членов в интерфейсах

Начиная с C # 8.0 интерфейс может определять реализацию по умолчанию для членов. Он также может определять статические члены, чтобы обеспечить единую реализацию общих функций.

интерфейс (Справочник по C #)

Например

public interface IGetSomething
{
    public static string Something = "something";
}

var something = IGetSomething.Something;
Кристиан Финдли
источник