C#中的新操作符没有覆盖基类成员

6

我对为什么new运算符不能按照我的期望工作感到困惑。

注意:所有下面列出的类都在同一命名空间和同一文件中定义。

这个类允许你在控制台输出任何内容前添加一些提供的文本。

public class ConsoleWriter
{
    private string prefix;

    public ConsoleWriter(string prefix)
    {
        this.prefix = prefix;
    }

    public void Write(string text)
    {
        Console.WriteLine(String.Concat(prefix,text));
    }
}

这是一个基类:

public class BaseClass
{
    protected static ConsoleWriter consoleWriter = new ConsoleWriter("");

    public static void Write(string text)
    {
        consoleWriter.Write(text);
    }
}

这是一个已实现的类:

public class NewClass : BaseClass
{
    protected new static ConsoleWriter consoleWriter = new ConsoleWriter("> ");
}

现在这里是执行此操作的代码:

class Program
{
    static void Main(string[] args)
    {
        BaseClass.Write("Hello World!");
        NewClass.Write("Hello World!");

        Console.Read();
    }
}

所以我期望的输出将会是:
Hello World!
> Hello World!

但是输出结果是:
Hello World!
Hello World!

我不明白为什么会出现这种情况。以下是我的思路:
  1. CLR调用BaseClass.Write()方法。
  2. CLR初始化BaseClass.consoleWriter成员变量。
  3. 方法被调用并使用BaseClass.consoleWriter变量执行。
然后,
  1. CLR调用NewClass.Write()方法。
  2. CLR初始化NewClass.consoleWriter对象。
  3. CLR发现实现位于BaseClass中,但该方法是通过继承获得的。
  4. CLR使用NewClass.consoleWriter变量在本地(在NewClass中)执行方法。
我认为这就是继承结构的工作方式?
请有人帮我理解为什么这样做不起作用?
更新如下:
此场景将按以下方式进行(我已实现):
public class LogBase
{
   protected static TraceSource logger = new TraceSource("");

   public static void Error (string text) { logger.WriteError(text); }
   public static void Info (string text) { logger.WriteInformation(text); }
   public static void Warning (string text) { logger.WriteWarning(text); }
   public static void Verbose (string text) { logger.WriteVerbose(text); }
}

// DataAccess logging
public class DALog : LogBase
{
    protected new static TraceSource logger = new TraceSource("DataAccess");
}

// BusinessObjects logging
public class BOLog : LogBase
{
    protected new static TraceSource logger = new TraceSource("Objects");
}

// BusinessLogic logging
public class BLLog : LogBase
{
    protected new static TraceSource logger = new TraceSource("Logic");
}

// WebUI logging
public class WebUILog : LogBase
{
    protected new static TraceSource logger = new TraceSource("WebUI");
}

原因是我不需要为每个类重复编写代码。

--

更新(选择解决方案后):

为了解决这个问题,我没有使用基类,而是定义了一个包含功能的类。然后创建了新的类,每个层级都持有单例实例:

public sealed class LogBase
{
   private TraceSource logger = null;

   public static LogBase GetLogger(string loggerName) 
   {
       return new LogBase(loggerName);
   }

   private LogBase(string loggerName) { logger = new TraceSource(loggerName); }

   public void Error (string text) { logger.WriteError(text); }
   public void Info (string text) { logger.WriteInformation(text); }
   public void Warning (string text) { logger.WriteWarning(text); }
   public void Verbose (string text) { logger.WriteVerbose(text); }
}

// DataAccess logging - no base class
public class DALog 
{
    private static LogBase logger;

    public static LogBase Instance
    { 
         get 
         { 
              if (logger==null) { logger = new TraceSource("DataAccess"); }
              return logger;
         }
    }
}

...
4个回答

9
new运算符并没有真正地实现多态的覆盖。它只是添加了另一个方法,该方法“恰好”具有相同的签名,但被视为单独的方法。只有在子类上显式执行该方法时,“new”实现才会被使用。
在您的情况下,您只在基类上实现了Write。在那里,您调用了consoleWriter的行。那一行引用了基类,因此使用原始实现。它甚至不知道子类中的附加实现。
请检查您的代码,并将其视为:
public class BaseClass
{
    protected static ConsoleWriter consoleWriter = new ConsoleWriter("");

    public static void Write(string text)
    {
        consoleWriter.Write(text);
    }
}


public class NewClass : BaseClass
{
    protected static ConsoleWriter anotherConsoleWriter = new ConsoleWriter(">");
}

你的 BaseClass.Write 永远不会考虑调用另一个 consoleWriter 而不是 consoleWriter。
如果你想让你的示例工作,你需要添加一个新的 Write 实现:
public class NewClass : BaseClass
{
    protected new static ConsoleWriter consoleWriter = new ConsoleWriter(">");

    public static new void Write(string text)
    {
        consoleWriter.Write(text);
    }
}

...或者,你最初可能想要的是使用override而不是new,从而在多态的意义上引入真正的覆盖。然而,您的基类需要通过声明consoleWriter为virtual来支持此操作。另外(如您在评论中提到的),只有当您不将这些方法设为静态时才能起作用。

如果您仍希望对记录器进行静态访问,可以应用Singleton模式,或者查看log4net,它已经为您解决了所有这些问题。


我不能将静态方法声明为虚拟的。也许我应该返回类的实例,然后调用非静态方法来解决这个问题? - Dominic Zukiewicz
可以这样做。您可以将其设置为单例,并仍然提供一种静态访问方式,例如:LogBase.GetLogger("DataAccess")。然后,您几乎可以看到它的外观类似于log4net,您可能需要查看一下。 - chiccodoro

3
你缺少了一些关键点。Static使它成为类级别的字段。Protected表示它可以从派生类中访问。New表示你要隐藏基本成员。
如果不需要这些类是静态的,那么你可以将Write方法设置为虚拟的,并在派生类中重写它;base.Write(">" + text);

我想它是静态的,因为我可以在基类中定义所有方法,然后调用静态成员(实际实现中是TraceSource)。因此,关于我继承的基类,我唯一需要更改的就是覆盖的实现。我将更新问题。 - Dominic Zukiewicz

3

其他人已经回答了原始问题:覆盖方法得到的行为是 OP 期望的行为,而使用 new 得到的行为不是 OP 期望的行为。

在 OP 的回复中,他关心的是您无法覆盖静态方法。这是正确的,但这里有一篇来自 msdn 的博客,既解释了原因,又讨论了处理它的方法:链接


1

你知道继承和重写只适用于(虚拟)实例方法,而不是静态方法,对吗?

你的重写是protected,因此在NewClass及其子类之外不可见。因此,这种行为是按规范进行的。new只允许你创建一个新函数,该函数可以在其上下文中通过相同的标识符引用:你的NewClass.Write方法与BaseClass.Write没有任何关系。


谢谢Pontus。所有的关键字都是为了特定目的而选择的,但我现在明白它们是不正确的。原因如下:'protected'是因为我只想让子类看到成员,以便覆盖它。'new'是因为我希望该方法使用被覆盖的版本。而'static'则是因为该成员将与静态方法一起使用(实际上是为了方便,而不是每次都要“new”)。但正如我们在这里所看到的,静态成员和方法是不会被继承的。 - Dominic Zukiewicz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接