传递类型作为构造函数参数或通用

时间:2017-07-19 01:12:26

标签: c# .net

我使用以下界面

创建了一个记录器
public interface ILogger
{
    void Log(LogLevel logLevel, string message, [CallerMemberName] string callingMemberName = "");
    Exception Log(Exception ex, [CallerMemberName] string callingMemberName = "");
}

调用Log()时我想要打印的一件事是应该打印方法名称及其Type。使用[CallerMemberName]属性很容易获得方法名称。要获得类型,我需要使用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
}

两者都要求每个类都有自己的ILogger实例,但我对此感到满意。

这就是每个人的看法:

//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);

问题是,有没有理由更喜欢一种方法而不是另一种方法?

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:-1)

两个主要差异

运行时与编译时

使用泛型,您必须在编译时知道类型。如果您的日志记录代码嵌入在帮助程序库中,这可能有点棘手,因为辅助方法或类也必须公开泛型参数以便传递它。同时,使用构造函数参数方法,您可以在运行时确定类型,并将其作为Type或甚至作为string传递,没有任何问题。

静电故障

每个&#34;版本&#34;泛型类是它自己的.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
}

在此示例中,FileStream的所有实例都会共享一个TextWriterLogger。简单,直接,有意义;毕竟,只有一个文件,为什么要打开多个句柄?

现在看一下泛型:

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>不同。由于它是一种不同的类型,因此它具有一组不同的静态变量。在这种情况下,每次实例化一种新类型的记录器时,您都在运行一个新的静态构造函数,并尝试在与所有其他记录器相同的文件上打开句柄。这可能最终导致资源争用或其他非预期的副作用,例如,你甚至无法打开文件。

你可以通过嵌入另一个非泛型类来解决这个问题,并让嵌入式类包含静态成员。或者,您可以注入一个每进程实例的类,并在成员变量中保存您需要的内容。就个人而言,我认为这会增加不必要的复杂性,这应该是非常简单的事情。

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