Условный атрибут C# на элементе интерфейса

Я пытаюсь избавиться от директив "#if TRACE" в моем коде, используя вместо этого атрибут Conditional, но не могу легко применить этот подход к интерфейсам. У меня есть способ обойти это, но это довольно уродливо, и я ищу лучшее решение.

Например, у меня есть интерфейс с условно скомпилированным методом.

interface IFoo
{
#if TRACE
    void DoIt();
#endif
}

Я не могу использовать условный атрибут в интерфейсе:

// Won't compile.
interface IFoo
{
    [Conditional("TRACE")]
    void DoIt();
}

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

interface IFoo
{
    void TraceOnlyDoIt();
}

class Foo : IFoo
{
    public void TraceOnlyDoIt()
    {
        DoIt();
    }

    [Conditional("TRACE")]
    void DoIt()
    {
        Console.WriteLine("Did it.");
    }
}

Это оставило бы мой клиентский код с избыточными вызовами к методу 'nop' TraceOnlyDoIt() в сборке не TRACE. Я могу обойти это с помощью метода условного расширения на интерфейсе, но он становится немного уродливым.

interface IFoo
{
    void TraceOnlyDoIt();
}

class Foo : IFoo
{
    public void TraceOnlyDoIt()
    {
        Console.WriteLine("Did it.");
    }
}

static class FooExtensions
{
    [Conditional("TRACE")]
    public static void DoIt(this IFoo foo)
    {
        foo.TraceOnlyDoIt();
    }
}

Есть лучший способ сделать это?

5 ответов

Решение

Метод трассировки не должен появляться в интерфейсе, так как это деталь реализации.

Но если вы застряли с интерфейсом и не можете его изменить, я бы использовал #if ... #endif подход, с которого вы начали.

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

Мне нравится метод расширения метода. Это может быть сделано немного лучше / надежнее, по крайней мере для абонентов:

    public interface IFoo
    {
        /// <summary>
        /// Don't call this directly, use DoIt from IFooExt
        /// </summary>
        [Obsolete]
        void DoItInternal();
    }

    public static class IFooExt
    {
        [Conditional("TRACE")]
        public static void DoIt<T>(this T t) where T : IFoo
        {
#pragma warning disable 612
            t.DoItInternal();
#pragma warning restore 612
        }
    }

    public class SomeFoo : IFoo
    {
        void IFoo.DoItInternal() { }

        public void Blah()
        {
            this.DoIt();
            this.DoItInternal(); // Error
        }
    }

Общие ограничения типов используются, чтобы избежать виртуального вызова и потенциальной упаковки типов значений: оптимизатор должен хорошо с этим справиться. По крайней мере, в Visual Studio он генерирует предупреждения, если вы вызываете внутреннюю версию через из-за устаревания. Явная реализация интерфейса используется для предотвращения случайного вызова внутренних методов для конкретных типов: также помечает их как [Устаревшие].

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

Как насчет этого:

interface IFoo
{
  // no trace here
}

class FooBase : IFoo
{
#if TRACE
    public abstract void DoIt();
#endif
}

class Foo : FooBase
{
#if TRACE
    public override void DoIt() { /* do something */ }
#endif
}

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

interface IFoo
{
    void DoIt();
}

class NullFoo : IFoo
{
    public void DoIt()
    {
      // do nothing
    }
}

class TraceFoo : IFoo
{
    public void DoIt()
    {
        Console.WriteLine("Did it.");
    }
}

Вы должны оставить задачу оптимизатору (или JIT-компилятору) и использовать:

interface IWhatever
{
  void Trace(string strWhatever);
}

class CWhatever : IWhatever
{
    public void Trace(string strWhatever)
    {
#if TRACE
       // Your trace code goes here
#endif
    }
}

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

Другие вопросы по тегам