我使用以下界面
创建了一个记录器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);
问题是,有没有理由更喜欢一种方法而不是另一种方法?
答案 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
的所有实例都会共享一个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>
不同。由于它是一种不同的类型,因此它具有一组不同的静态变量。在这种情况下,每次实例化一种新类型的记录器时,您都在运行一个新的静态构造函数,并尝试在与所有其他记录器相同的文件上打开句柄。这可能最终导致资源争用或其他非预期的副作用,例如,你甚至无法打开文件。
你可以通过嵌入另一个非泛型类来解决这个问题,并让嵌入式类包含静态成员。或者,您可以注入一个每进程实例的类,并在成员变量中保存您需要的内容。就个人而言,我认为这会增加不必要的复杂性,这应该是非常简单的事情。
除非有令人信服的理由,否则我会避免在特定情况下使用泛型。