Условный атрибут 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-компилятор не удаляют вызов, вы должны написать гневные электронные письма этим разработчикам;).