C # статическая функция в интерфейсе - PullRequest
6 голосов
/ 07 марта 2011

Есть ли способ моделирования статической функции в интерфейсе в C #?

Я хочу использовать его для фабрики, в которой каждый объект наследуется от ICreateAble со статической функцией 'Create', а в классе фабрики вы можете затем вызывать Factory.Create(Type t, string fromFile), который вызывает t.Create()

Ответы [ 5 ]

5 голосов
/ 26 октября 2011

Этот вопрос задавался десятки раз, и обычно ответ отрицательный: от «ты не можешь» до «ты не должен» или «это не имеет смысла». Многие способные разработчики здесь, в 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, зарегистрировав их вместо этого.

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

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

Простое добавление статических интерфейсов не даст вам такой степени гибкости.

1 голос
/ 07 марта 2011

Кажется, ваш ICreateAble - фабрика классов. Обычный шаблон - иметь отдельную фабрику классов: вы можете определить IWhatnot и IWhatnotFactory. Если бы я реализовал IWhatnot, я бы написал фабричный класс, который также реализует IWhatnotFactory.

Более быстрым и грязным способом было бы добавить метод фабрики классов в IWhatnot и создать экземпляр неиспользованного в противном случае экземпляра класса (скажем, MyWhatnot) и использовать его в качестве фабрики классов. Это было бы ближе к тому, о чем ты говоришь. Разница в том, что есть объект, на который вы можете ссылаться: ваша коллекция фабрики классов - это коллекция ссылок на IWhatnot, а не IWhatnotFactory. Однако в любом из этих случаев у вас есть куча ссылок на интерфейс, и каждая ссылка ссылается на экземпляр класса.

Это особый случай более широкого вопроса: Почему в C # нет статических виртуальных методов?

Причина в том, что классы на самом деле не являются первоклассными объектами в C #, не так, как здесь было бы полезно. Ссылка типа IWhatnot относится к экземпляру чего-либо, реализующего IWhatnot. Там нет эквивалента для ссылки на класс. В C # вы можете иметь ссылку на «любой экземпляр класса, который подходит под это описание». У вас не может быть ссылки на «любой КЛАСС, который соответствует этому описанию» - кроме использования отражения, как вы предлагаете. Но метод, который может быть вызван только с помощью рефлексии, будет использоваться в качестве языковой функции.

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

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

UPDATE

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

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

1 голос
/ 07 марта 2011

Нет , интерфейсы в 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

0 голосов
/ 07 марта 2011

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

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

0 голосов
/ 07 марта 2011

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

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

В зависимости от природы объекта (состояния или поведения) я также рассмотрю возможность отделения творения от реализации. Это особенно верно, если объект является объектом состояния.

Мне бы хотелось узнать больше о том, что вы делаете, но я предполагаю, что у вас есть абстрактная фабрика, которая вызывает Create (), которая служит конкретной фабрикой ... внутри статического метода создаваемого объекта. Я не уверен, без дополнительного контекста, что это хорошая модель. И я склоняюсь к НЕ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...