[英]Pass In Type as Constructor Parameter or Generic
我使用以下界面创建了一个记录器
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);
问题是,有什么理由比另一种更喜欢一种方法吗?
两种方式都可以正常工作。
但是,如果情况有所不同,则可能需要考虑一些问题。 例如,如果T
是从某个其他类/实现派生的,则该接口(受约束的泛型)需要在代码中的某个地方调用方法,那么使用泛型会更有利,因为您可以直接调用该方法(即,非-generic将需要反射):
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
。
两个主要区别
运行时与编译时
使用泛型,您必须在编译时知道类型。 如果将日志记录代码嵌入到帮助程序库中,这可能会有些棘手,因为帮助程序方法或类也将必须公开通用参数才能传递。 同时,使用构造函数自变量方法,您可以在运行时确定类型,并将其作为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
}
在此示例中,有一个FileStream
和TextWriter
将由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类型。 由于它是不同的类型,因此它将具有不同的静态变量集。 在这种情况下,每次实例化一种新型的记录器时,您都在运行一个新的静态构造函数,并尝试与其他所有记录器一样在同一文件上打开一个句柄。 这可能最终导致资源争用或其他意外的副作用,例如,您甚至无法打开文件。
您可以通过在内部嵌入另一个非泛型类并让该嵌入式类包含静态成员来解决此问题。 或者,您可以注入每个进程实例类,然后在成员变量中保存所需的内容。 我个人认为应该为一些非常简单的事情增加不必要的复杂性。
除非有令人信服的理由,否则在这种情况下,我将避免使用泛型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.