Статическая функция C# в интерфейсе
есть ли способ имитировать статическую функцию в интерфейсе В C#?
Я хочу использовать его для фабрики, в которой каждый объект наследуется от ICreateAble
со статической функцией "Create" и в классе factory вы можете вызвать Factory.Create(Type t, string fromFile
), который называет t.Create()
5 ответов
перед "ответом" я хочу заявить, что вы должны освободить свою проблему от своего решения, когда задаете вопросы, поскольку вы, вероятно, получите ответ, который решит ваше решение, даже если это не лучший способ решить вашу основную проблему.
Если вы действительно обобщили создание объектов, я бы рассмотрел немного перевернуть этот шаблон и использовать дженерики для решения проблемы, а не искать средства для имитации статического метода на интерфейсе. Эта воля требуют переосмысления решения, конечно.
в зависимости от природы объекта (состояния или поведения) я бы также рассмотрел отделение создания от реализации. Это особенно верно, если объект является объектом состояния.
Мне нужно было бы увидеть больше того, что вы делаете, но я предполагаю, что у вас есть абстрактная фабрика, которая вызывает Create (), которая служит конкретной фабрикой ... внутри статического метода на создаваемом объекте. Я не уверен, без дополнительный контекст, что это хорошая картина. И я склоняюсь не к этому.
этот вопрос задавался десятки раз, и обычно ответ отрицательный, начиная от "вы не можете" до "вы не должны" или "это не имеет смысла". Многие способные разработчики здесь на так и различных блогах etc. пошли на многое, чтобы объяснить, почему эта функция не подходит для языка 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; }
}
вы необходимо использовать другой синтаксис для конструктора, и доступ к свойствам и вызов метода выглядят по - разному-но если вы решите сохранить только один глобальный экземпляр этого объекта, и вы решите создать этот экземпляр при запуске, функционально, нет никакой разницы.
суть в том, чтобы подготовить вас к следующей идее: ваши типы не должны быть статичными.
причина, по которой вы хотите функцию интерфейса, заключается в том, что вам нужно указать "контракт" на который ваш типы нужно соответствовать - но интерфейсы не работают для вас, потому что они определяют, как объекты нужно соответствовать.
мой ответ на это-просто реализовать ваши типы как конкретные объекты и сохранить их статически, а не полагаться на "статическое" ключевое слово для части вашего типа, которая должна соответствовать интерфейсу.
например, давайте рассмотрим тип животного, с подтипами кошки и собаки - и если предположить, что все животные определенного типа издают один и тот же звук, предположим, что наши животные должны издавать звук. Как вы уже знаете, следующее делает не работы:
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
, который является отражением самого определения типа, а не ссылкой на глобальный статический объект, который будет автоматически создан при запуске. С для абстрактных статических методов синтаксиса нет, следовательно, нет и синтаксиса для статического вызова реализации такого метода.
единственный способ получить доступ к свойству статического звука-это напрямую ссылаться на тип, например Cat.Sound
или Dog.Sound
. Хорошо, это не совсем так - вы можете получить доступ к методам с помощью отражения, но это, вероятно, будет не очень удобно. Конечно, вы также можете добавить еще одно нестатическое свойство в каждом животном типе для каждого свойство в родительском классе, которое явно обращается к статическим свойствам. Опять же, я не думаю, что это делает очень поддерживаемый подход, если у вас много типов животных...
забудьте о попытке использовать статические свойства и стандартный ввод для достижения желаемого-вместо этого давайте добавим конкретный тип, который указывает, что мы определить как тип животного:
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);
}
недостающая часть-это средство, которое позволяет нам работать с типами животных, когда все, что у нас есть, - это система.Введите, но не фактический экземпляр. Другими словами, мы знаем тип животного, и нам нужен доступ к его животному типу. Я придумал несколько решений для этого, некоторые из которых включают отражение, некоторые требуют пустого конструктора в животных-типах. Мое любимое решение-добавить простой реестр, который отображает каждый тип системы животных соответствует типу 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];
}
}
я считаю, что самый безопасный способ заполнить этот реестр-добавить статические конструкторы к каждому типу системы животных, например:
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; }
}
}
это требует небольшой дисциплины с вашей стороны, как и все, что связано с задачами, связанными с типом времени выполнения. Я считаю, что этот дополнительный бит работы предпочтительнее, чем использование отражения или пустых конструкторов для заполнения реестра.
наконец, мы можем добавить тест, демонстрация того, как использовать систему.Введите, чтобы получить 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. Предположим, вам нужны альтернативные реализации для разных типов животных-просто объявите AnimalType как абстрактный (или превратите его в интерфейс), а затем расширьте его в конкретные типы CatType и DogType, регистрируя их вместо этого.
наконец, подумайте о том, что на самом деле это намного более гибкий, чем статические интерфейсы, - в некотором смысле вы определяете свое собственное доменное определение "мета-типа". Преимущество заключается в том, что вы можете свободно моделировать свои мета-типы, используя наследование и интерфейсы, так же, как вы моделируете любую другую иерархию типов.
я не считаю это обходным путем в смысл, статические интерфейсы были бы просто новым синтаксисом для чего-то, что вы уже можете сделать лучше. Моделируя свои мета-типы как конкретные системные типы, вы получаете более обслуживаемую кодовую базу, которая будет масштабироваться для удовлетворения новых требований без сверхмощного рефакторинга; вы просто расширяете свои мета-типы так же, как расширяете любой другой тип.
простое добавление статических интерфейсов не даст вам такой степени гибкости.
нет, интерфейсы В C# не могут объявлять статические методы. Хорошее обсуждение этого вопроса можно найти здесь: http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/1ea9a71f-3678-4004-a164-016ab8626ed6
здесь: http://social.msdn.microsoft.com/Forums/en-US/clr/thread/693947c5-c00f-4524-bb27-2ea29e8fd2ea
похоже, что ваш ICreateAble-это фабрика классов. Обычный шаблон - иметь отдельную фабрику классов: вы можете определить IWhatnot и IWhatnotFactory. Если я реализую IWhatnot, я бы написал класс factory, который также реализует IWhatnotFactory.
более быстрым и грязным способом было бы добавить метод фабрики класса в IWhatnot и создать экземпляр экземпляра класса, который в противном случае не использовался (MyWhatnot, скажем), и использовать его как фабрику классов. Это было бы ближе к тому, о чем ты говоришь. о. Разница в том, что есть объект, на который вы можете иметь ссылку: ваша коллекция фабрики классов-это коллекция ссылок на IWhatnot, а не на IWhatnotFactory. В любом из этих случаев, однако, у вас есть куча ссылок на интерфейс, и каждая ссылка ссылается на экземпляр класса.
Это частный случай более широкого вопроса: почему у C# нет статических виртуальных методов?
причина в том, что классы не действительно первоклассные объекты в C#, а не так, как было бы полезно здесь. Ссылка типа IWhatnot ссылается на экземпляр чего-то, что реализует IWhatnot. Нет эквивалента для ссылки на класс. В C# вы можете иметь ссылку на"любой экземпляр класса, который соответствует этому описанию". Вы не можете иметь ссылку на" любой класс, который соответствует этому описанию " - кроме использования отражения, как вы предлагаете. Но метод, который может быть вызван только через отражение, получит где угодно, как функция языка.
шаблон фабрики классов дает вам первоклассный объект, который создает экземпляр - вы можете думать об этом как об обходном пути для невозможности использовать первоклассные классы в качестве фабрик для своих собственных экземпляров.
было бы здорово, если бы вы могли, но это была бы большая функция, чем кажется на первый взгляд. Это, вероятно, намного больше, чем я понимаю, так как я не проработал все последствия. Языковой дизайн-это весело.
обновление
Что вы можете сделать, это свернуть свой собственный: типы запросов для статического метода с заданным именем и прототипом. Вы не можете заставить компилятор применить его, но вы можете документировать его в сообщении об ошибке во время выполнения.
тем не менее, для ремонтопригодности я бы рекомендовал шаблон фабрики класса, а не какой-то экзотический новый способ сделать то же самое.
вы не можете объявить статические методы на интерфейсе, потому что это не то, для чего предназначены интерфейсы.
ваш лучший курс действий-создать интерфейс для всего, что вы хотите, чтобы ваш класс реализовал, а затем создать абстрактный/базовый класс, который вы хотите, чтобы ваша фабрика унаследовала. Ваш базовый класс может иметь статические методы, которые вы желаете.