Подтвердить что ты не робот

Принудительные причины использования маркерных интерфейсов вместо атрибутов

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

Избегайте использования интерфейсов маркеров (интерфейсы без элементов).

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

Там даже правило FxCop для обеспечения этой рекомендации:

Избегайте пустых интерфейсов

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

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

В статье указывается только одна причина, по которой вы можете игнорировать предупреждение: когда вам нужна идентификация времени компиляции для типов. (Это согласуется с конструкцией интерфейса).

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

Вот и возникает актуальный вопрос: Microsoft не соответствовала их собственной рекомендации в дизайне библиотеки классов Framework (по крайней мере, в нескольких случаях): Интерфейс IRequiresSessionState и Интерфейс IReadOnlySessionState. Эти интерфейсы используются инфраструктурой ASP.NET, чтобы проверить, должно ли оно включать состояние сеанса для конкретного обработчика или нет. Очевидно, что он не используется для идентификации времени типа компиляции. Почему они этого не сделали? Я могу думать о двух потенциальных причинах:

  • Микро-оптимизация: проверка того, реализует ли объект интерфейс (obj is IReadOnlySessionState), быстрее, чем использование отражения для проверки атрибута (type.IsDefined(typeof(SessionStateAttribute), true)). Разница незначительна в большинстве случаев, но на самом деле это может иметь значение для критического кода производительности в среде выполнения ASP.NET. Однако есть обходные пути, которые они могли бы использовать как кеширование результата для каждого типа обработчика. Интересно, что веб-службы ASMX (которые подчиняются аналогичным характеристикам производительности) фактически используют свойство EnableSession WebMethod атрибут для этой цели.

  • Возможность реализации интерфейсов потенциально более вероятно будет поддерживаться, чем декорирование типов с помощью атрибутов сторонними .NET-языками. Поскольку ASP.NET разработан как агностический язык, а ASP.NET генерирует код для типов (возможно, на стороннем языке с помощью CodeDom), которые реализуют упомянутые интерфейсы на основе атрибута EnableSessionState директивы <%@ Page %>, может возникнуть смысл использовать интерфейсы вместо атрибутов.

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

Это просто (преждевременная?) оптимизация или крошечная ошибка в дизайне структуры? (Думают ли они отражение - это "большой монстр с красными глазами" ?) Мысли?

4b9b3361

Ответ 1

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

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

Ответ 2

Для универсального типа вы можете использовать один и тот же общий параметр в интерфейсе маркера. Это невозможно для атрибута:

interface MyInterface<T> {}

class MyClass<T, U> : MyInterface<U> {}

class OtherClass<T, U> : MyInterface<IDictionary<U, T>> {}

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

Еще одно хорошее использование интерфейса маркера - это когда вы хотите создать вид mixin:

interface MyMixin {}

static class MyMixinMethods {
  public static void Method(this MyMixin self) {}
}

class MyClass : MyMixin {
}

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

UPDATE:

Я не знаю, учитывается ли это, но я использовал их для обозначения классов для post-compiler для работы.

Ответ 3

Microsoft не строго придерживалась руководящих принципов, когда они сделали .NET 1.0, потому что рекомендации эволюционировали вместе с каркасом и некоторые правила, которые они не изучали, пока не стало слишком поздно менять API.

IIRC, примеры, которые вы упомянули, относятся к BCL 1.0, чтобы это объяснило.

Это объясняется в Руководстве по разработке каркаса.


Тем не менее, в книге также отмечается, что "[A] ttribute тестирование намного дороже, чем проверка типа" (на боковой панели Рико Мариани).

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

Ответ 4

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

Ответ 5

С точки зрения кодирования, я думаю, что предпочитаю синтаксис интерфейса Marker из-за встроенных ключевых слов as и is. Маркировка атрибутов требует немного больше кода.

[MarkedByAttribute]
public class MarkedClass : IMarkByInterface
{
}

public class MarkedByAttributeAttribute : Attribute
{
}

public interface IMarkByInterface
{
}

public static class AttributeExtension
{
    public static bool HasAttibute<T>(this object obj)
    {
        var hasAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(T));
        return hasAttribute != null;
    }
}

И некоторые тесты для использования кода:

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ClassMarkingTests
{
    private MarkedClass _markedClass;

    [TestInitialize]
    public void Init()
    {
        _markedClass = new MarkedClass();
    }

    [TestMethod]
    public void TestClassAttributeMarking()
    {
        var hasMarkerAttribute = _markedClass.HasAttibute<MarkedByAttributeAttribute>();
        Assert.IsTrue(hasMarkerAttribute);
    }

    [TestMethod]
    public void TestClassInterfaceMarking()
    {
        var hasMarkerInterface = _markedClass as IMarkByInterface;
        Assert.IsTrue(hasMarkerInterface != null);            
    }
}