2017-07-19 86 views
1

我创建了一个记录器具有以下接口传递类型为构造函数的参数或通用

public interface ILogger 
{ 
    void Log(LogLevel logLevel, string message, [CallerMemberName] string callingMemberName = ""); 
    Exception Log(Exception ex, [CallerMemberName] string callingMemberName = ""); 
} 

一个我想要打印的东西时Log()被称为是该方法的名字连同其Type应打印。方法名称易于使用[CallerMemberName]属性获取。要获得类型I或者需要使用StackTrace(这是缓慢和不可预知的)或通过它。

我决定我想通过它,但想出了办法做到这一点。

1)它传递到构造

public abstract class AbstractLogger : ILogger 
{ 
    protected LogLevel minLogLevel; 
    protected Type callingMemberType; 

    protected AbstractLogger(Type callingMemberType, LogLevel minLogLevel) 
    { 
     this.callingMemberType = callingMemberType; 
     this.minLogLevel = minLogLevel; 
    } 

    //abstract methods omitted 
} 

2)传递它作为一种通用

public abstract class AbstractLogger<T> : ILogger 
{ 
    protected LogLevel minLogLevel; 
    protected Type callingMemberType; 

    protected AbstractLogger(LogLevel minLogLevel) 
    { 
     this.callingMemberType = typeof(T); 
     this.minLogLevel = minLogLevel; 
    } 

    //abstract methods omitted 
} 

两者都要求每个班都有自己的​​实例,但我确定接着就,随即。

这是呼叫会是什么样子为他们每个人:

//pass in to constructor 
public ILogger MyLogger = new ConcreteLogger(typeof(MyClass, LogLevel.DEBUG); 

//pass as a generic 
public ILogger MyLogger = new ConcreteLogger<MyClass>(LogLevel.DEBUG); 

的问题是,没有任何理由,更喜欢一个方法比其他?

+0

就我个人而言,因为您实际上并不是在编写泛型代码,所以我会在'Type'中传入,但是您可能会发现另一个看起来更好。但我个人建议只使用现有的库,而不是滚动自己的日志代码。 – juharr

+0

我知道“泛型”是在编译时解决的,而“typeof”方法可能是在运行时。 –

回答

0

这两种方式都在你的情况下正常工作。

但是如果情况不同,可能会有一些考虑。例如,如果T从其他类派生/实现了一个接口(约束泛型),以至于它需要调用代码中的某个方法,那么使用泛型更有利,因为您可以直接调用该方法(即非 - 通用将需要反射):

public class Foo { 
    public void Execute() { } 
    } 

    public class Bar<T> where T : Foo { //constrained generic 
    public T val; 
    public Bar(T input){ 
     val = input; 
    } 
    public void SomeFunction() { 
     val.Execute(); //generic is better since it can call this "Execute" method without Reflection 
    } 
    } 

但在你的情况,这是没有必要的。在有进一步的代码之前,两种情况都应该没问题。

我个人更喜欢根据需要编码。在这种情况下,不需要通用的,我会使用Type

+0

只需声明'public void SomeFunction(Foo obj)',您就可以轻松地调用'没有泛型的'Execute'。基本多态性。你当然不需要思考。 –

+0

@JohnWu谢谢你指出,我想我的例子应该跟随什么OP写入,构造函数中的输入。这样,我的意思就更清楚了。 – Ian

-1

两个主要差别

运行时与编译时

使用泛型,你必须知道在编译时的类型。如果你的日志代码嵌入到辅助库中,这可能有点棘手,因为辅助方法或类也必须公开一个通用参数才能传递它。同时,使用构造函数参数方法,您可以在运行时确定类型,并将其作为Type甚至作为string传递,而不会出现问题。

静态麻烦

每一个泛型类的“版本”是自己的.NET类型。这意味着每个人都有自己的静态构造函数和变量。这可以产生巨大的差异。

想象一下你的记录保持单手柄,每个进程的文件输出:

class Logger 
{ 
    static private FileStream _fileStream; 
    static private TextWriter _writer; 

    static void Logger() 
    { 
     var config = ReadConfigurationFile(); 
     _fileStream = new FileStream(config.path); 
     _writer = new TextWriter(_fileStream); 
    } 
} 

void Main() 
{ 
    var l1 = new Logger("MyType"); //On first invocation, will fire static constructor and reserve the file 
    var l2 = new Logger("SomeOtherType"); //Static constructor has already run, and won't run again 
} 

在这个例子中,有一个FileStreamTextWriter将通过Logger所有实例共享。简单,直接,有道理;毕竟,只有一个文件,为什么打开多个句柄?

现在来看看泛型:

class Logger<T> where t : class 
{ 
    static private FileStream _fileStream; 
    static private TextWriter _writer; 

    static void Logger() 
    { 
     var config = ReadConfigurationFile(); 
     _fileStream = new FileStream(config.path); 
     _writer = new TextWriter(_fileStream); 
    } 
} 

void Main() 
{ 
    var l1 = new Logger<HomePage>(); //Fires the static constructor for Logger<HomePage> on first invocation 
    var l2 = new Logger<HelpPage>(); //Fires a different static constructor, and will try to open the file a second time 
} 

在这种模式下,一个Logger<HomePage>在技术上是从Logger<HelpPage>不同的.NET类型。由于它是一个不同的类型,它会有一组不同的静态变量。在这种情况下,每次实例化新类型的记录器时,都会运行一个新的静态构造函数,并尝试在所有其他记录器的相同文件上打开一个句柄。这可能最终导致资源争夺或其他意想不到的副作用,例如你甚至无法打开文件。

你可以通过嵌入另一个非泛型的类来让这个嵌入类包含静态成员。或者你可以注入一个每个进程的实例类,并在成员变量中保存你需要的东西。就我个人而言,我认为这增加了不必要的复杂性,应该很简单。

除非有令人信服的理由,否则我会避免在此特定情况下使用泛型。

相关问题