Этот вопрос задавался десятки раз, и обычно ответ отрицательный: от «ты не можешь» до «ты не должен» или «это не имеет смысла». Многие способные разработчики здесь, в SO, а также в различных блогах и т. Д., Приложили немало усилий, чтобы объяснить, почему эта функция не подходит для языка C #.
Но суть в том, что есть допустимые сценарии использования для статических интерфейсов - я часто их использую в PHP, и, конечно, есть способ добиться чего-то подобного в C #. Вам не нужно размышлять или взламывать, вам просто нужно немного изменить свое мышление.
Прежде всего, рассмотрим, что именно означает "статический":
public static class Foo
{
static Foo()
{
Value = "test";
}
public static string Value { get; set; }
}
В некотором смысле класс Foo - это просто глобальный одноэлементный объект со свойством Bar. Этот объект создается автоматически при запуске, и вы не можете создать более одного экземпляра, но в остальном он функционально эквивалентен следующему нестатическому классу:
public class Bar
{
public Bar()
{
Value = "test";
}
public string Value { get; set; }
}
Вам нужно использовать другой синтаксис для конструктора, а доступ к свойству и вызов метода выглядят по-разному - но если вы решите сохранить только один глобальный экземпляр этого объекта, и вы решите создать этот экземпляр при запуске, функционально, там нет разницы.
Суть в том, чтобы подготовить вас к следующей идее: ваши типы не должны быть статичными.
Причина, по которой вам нужна функция интерфейса, заключается в том, что вам нужно указать «контракт», которому должны соответствовать ваши типы - но интерфейсы у вас не работают, потому что они указывают как объекты должны соответствовать.
Мой ответ на этот вопрос заключается в том, чтобы просто реализовать ваши типы как конкретные объекты и хранить их статически, а не полагаться на ключевое слово «static» для части вашего типа, которая должна соответствовать интерфейсу.
Например, давайте рассмотрим тип Animal с подтипами Cat и Dog - и предположим, что все животные определенного типа издают одинаковый звук, скажем, что наши типы животных должны обеспечивать звук. Как вы уже знаете, не работает следующим образом:
public abstract class Animal
{
public static abstract string Sound { get; }
}
public class Cat : Animal
{
public static string Sound
{
get { return "Mee-oww."; }
}
}
public class Dog : Animal
{
public static string Sound
{
get { return "Woof!"; }
}
}
public void Test()
{
Animal cat = new Cat();
Animal dog = new Dog();
Assert.AreEqual(cat.GetType().Sound, Cat.Sound);
Assert.AreEqual(dog.GetType().Sound, Dog.Sound);
}
Помимо того, что static abstract
не поддерживается, другая серьезная проблема с тестом состоит в том, что cat.GetType()
возвращает System.Type
, который является отражением самого определения типа, а не ссылкой на глобальный статический объект, который будет автоматически создан при запуске. Поскольку нет синтаксиса для абстрактных статических методов, отсюда следует, что также нет синтаксиса для статического вызова реализации такого метода.
Единственный способ получить доступ к статическому свойству Sound - это непосредственная ссылка на тип, например, Cat.Sound
или Dog.Sound
. Ладно, это не совсем верно - вы можете получить доступ к методам, используя отражение, но это, вероятно, будет не очень удобно. Конечно, вы также можете добавить другое нестатическое свойство в каждый тип Animal для каждого свойства в родительском классе, которое явно обращается к статическим свойствам. Опять же, я не думаю, что это делает подход более приемлемым, если у вас много типов животных ...
Давайте начнем сначала.
Забудьте о попытке использовать статические свойства и стандартную типизацию для достижения того, чего вы хотите - вместо этого давайте добавим конкретный тип, который определяет, что мы определяем как тип животного:
public class AnimalType
{
public AnimalType(string sound)
{
Sound = sound;
}
public string Sound { get; private set; }
}
Так как System.GetType()
работает только для системных типов, нам нужно подобное средство для типов животных, и мы оставим это нереализованным - заставляя каждый конкретный тип животных предоставлять следующее:
public abstract class Animal
{
public abstract AnimalType AnimalType { get; }
}
Теперь мы можем реализовать конкретные типы животных - поскольку мы хотим, чтобы один AnimalType сопровождал каждый системный тип, который расширяет класс Animal, мы определим и сохраним экземпляр AnimalType в статическом поле внутри каждого типа - и нашу реализацию Свойство AnimalType вернет этот статический экземпляр:
public class Dog : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Woof!");
override public AnimalType AnimalType
{
get { return Type; }
}
}
public class Cat : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");
override public AnimalType AnimalType
{
get { return Type; }
}
}
Теперь мы можем написать рабочий тест:
public void StaticMethodInterface()
{
Animal dog = new Dog();
Animal cat = new Cat();
Assert.AreEqual(dog.AnimalType.Sound, Dog.Type.Sound);
Assert.AreEqual(cat.AnimalType.Sound, Cat.Type.Sound);
}
Недостающий элемент - это средство, которое позволяет нам работать с типами животных, когда у нас есть только System.Type, но не фактический экземпляр.Другими словами, мы знаем тип Animal, и нам нужен доступ к его AnimalType.Я придумал несколько решений, некоторые из которых включали рефлексию, некоторые требовали пустого конструктора в типах Animal.Мое любимое решение - добавить простой реестр, который сопоставляет каждый тип системы Animal с соответствующим ему AnimalType:
public class AnimalType
{
public AnimalType(string sound)
{
Sound = sound;
}
public string Sound { get; private set; }
private static IDictionary<Type, AnimalType> _types = new Dictionary<Type, AnimalType>();
public static void Register(Type type, AnimalType animalType)
{
_types.Add(type, animalType);
}
public static AnimalType Get(Type type)
{
return _types[type];
}
}
Я считаю, что самый безопасный способ заполнить этот реестр - это добавить статические конструкторы в каждую систему Animal.-type, вот так:
public class Dog : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Woof!");
static Dog()
{
AnimalType.Register(typeof(Dog), Type);
}
override public AnimalType AnimalType
{
get { return Type; }
}
}
public class Cat : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");
static Cat()
{
AnimalType.Register(typeof(Cat), Type);
}
override public AnimalType AnimalType
{
get { return Type; }
}
}
Это требует небольшой дисциплины с вашей стороны, как и все, что связано с задачами, связанными с типом во время выполнения.Я считаю, что эта дополнительная часть работы предпочтительнее использования отражающих или пустых конструкторов для заполнения реестра.
Наконец, мы можем добавить тест, демонстрирующий, как использовать System.Type для получения AnimalType, когда мыУ меня нет экземпляра:
public void Test()
{
var dogType = typeof (Dog);
var catType = typeof (Cat);
Assert.AreEqual(Dog.Type.Sound, AnimalType.Get(dogType).Sound);
Assert.AreEqual(Cat.Type.Sound, AnimalType.Get(catType).Sound);
}
Наконец, вот полный пример и тест:
public class AnimalType
{
public AnimalType(string sound)
{
Sound = sound;
}
public string Sound { get; private set; }
private static IDictionary<Type, AnimalType> _types = new Dictionary<Type, AnimalType>();
public static void Register(Type type, AnimalType animalType)
{
_types.Add(type, animalType);
}
public static AnimalType Get(Type type)
{
return _types[type];
}
}
public abstract class Animal
{
public abstract AnimalType AnimalType { get; }
}
public class Dog : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Woof!");
static Dog()
{
AnimalType.Register(typeof(Dog), Type);
}
override public AnimalType AnimalType
{
get { return Type; }
}
}
public class Cat : Animal
{
public static readonly AnimalType Type = new AnimalType(sound: "Mee-oww.");
static Cat()
{
AnimalType.Register(typeof(Cat), Type);
}
override public AnimalType AnimalType
{
get { return Type; }
}
}
public void Test()
{
Animal dog = new Dog();
Animal cat = new Cat();
Assert.AreEqual(dog.AnimalType.Sound, Dog.Type.Sound);
Assert.AreEqual(cat.AnimalType.Sound, Cat.Type.Sound);
var dogType = typeof (Dog);
var catType = typeof (Cat);
Assert.AreEqual(Dog.Type.Sound, AnimalType.Get(dogType).Sound);
Assert.AreEqual(Cat.Type.Sound, AnimalType.Get(catType).Sound);
}
Обратите внимание, что в этом простом пошаговом руководстве я использую один AnimalType ирегистрация экземпляра для каждого типа системы Animal.Предположим, вам нужны альтернативные реализации для различных типов Animal - просто объявите AnimalType как абстрактный (или превратите его в интерфейс), а затем расширьте его до конкретных типов CatType и DogType, зарегистрировав их вместо этого.
Наконец, возьмитемомент, чтобы задуматься о том, что это на самом деле намного более гибко, чем статические интерфейсы были бы - в некотором смысле, вы определяете свое собственное доменное определение «мета-типа».Преимущество состоит в том, что вы можете свободно моделировать свои мета-типы, используя наследование и интерфейсы, так же, как вы моделируете любую другую иерархию типов.
Я не считаю это обходным решением - в некотором смысле, статическимИнтерфейсы будут просто новым синтаксисом для чего-то, что вы уже можете делать лучше.Моделируя ваши мета-типы как конкретные системные типы, вы получаете более поддерживаемую кодовую базу, которая будет расширяться для соответствия новым требованиям без интенсивного рефакторинга;вы просто расширяете свои мета-типы так же, как вы расширяете любой другой тип.
Простое добавление статических интерфейсов не даст вам такой степени гибкости.