【问题标题】:C# 方法重载和接口
【发布时间】:2022-01-23 14:26:34
【问题描述】:

在基础知识方面摸不着头脑。

我有几个(例如)消息类共享一个公共基类。我有一个接受基类作为参数的接口。到目前为止,我认为可以很容易地使用方法重载来处理单独方法中的特定类型的消息。

您如何运行以下示例:

    using System;

namespace MethodOverloading
{

    // for the example: we are sending messages
    // which have a common base class
    public class MessageBase
    {
        public readonly string Value;
        public MessageBase() { Value = GetType().Name; }
    }

    // and there are a couple of concrete instances 
    public class Message1000 : MessageBase { }
    public class Message2000 : MessageBase { }
    public class Message3000 : MessageBase { }
    public class Message4000 : MessageBase { }
    public class Message5000 : MessageBase { }

    // and of cource we have an interface receiving all messages but only with one method for the base defined
    public interface IHandler
    {
        void ReceiveMessage(MessageBase msg);
    }

    // the handlers should do some method overloading so a overloaded method can be implemented for each supported message
    // and the base message catches all unsupported messages (e.g. log: ey, missed an overload for this type)

    // Handler 1 tries to overload the interface method
    public class Handler1 : IHandler
    {
        public void ReceiveMessage(MessageBase msg) { Console.WriteLine($"Handler1.ReceiveMessage(MessageBase:{msg.Value})"); }
        public void ReceiveMessage(Message1000 msg) { Console.WriteLine($"Handler1.ReceiveMessage(Message1000:{msg.Value})"); }
        public void ReceiveMessage(Message2000 msg) { Console.WriteLine($"Handler1.ReceiveMessage(Message2000:{msg.Value})"); }
        public void ReceiveMessage(Message3000 msg) { Console.WriteLine($"Handler1.ReceiveMessage(Message3000:{msg.Value})"); }
        public void ReceiveMessage(Message4000 msg) { Console.WriteLine($"Handler1.ReceiveMessage(Message4000:{msg.Value})"); }
        // intentionally no overload for Message5000
    }

    // Handler 2 provides one interface method and has protected overloads...
    public class Handler2 : IHandler
    {
        public void ReceiveMessage(MessageBase msg)   
        { 
            Console.Write($"Handler2.ReceiveMessage(MessageBase:{msg.Value}) > "); 
            HandleMessage(msg); 
        }
        protected void HandleMessage(MessageBase msg) { Console.WriteLine($"Handler2.HandleMessage(MessageBase:{msg.Value})"); }
        protected void HandleMessage(Message1000 msg) { Console.WriteLine($"Handler2.HandleMessage(Message1000:{msg.Value})"); }
        protected void HandleMessage(Message2000 msg) { Console.WriteLine($"Handler2.HandleMessage(Message2000:{msg.Value})"); }
        protected void HandleMessage(Message3000 msg) { Console.WriteLine($"Handler2.HandleMessage(Message3000:{msg.Value})"); }
        protected void HandleMessage(Message4000 msg) { Console.WriteLine($"Handler2.HandleMessage(Message4000:{msg.Value})"); }
        // intentionally no overload for Message5000
    }

    class Program
    {
        static void Main(string[] args)
        {

            // so lets give it a try ....
            Console.WriteLine("Testing method overloads");

            MessageBase msgBase = new MessageBase();
            Message1000 msg1000 = new Message1000();
            Message2000 msg2000 = new Message2000();
            Message3000 msg3000 = new Message3000();
            Message4000 msg4000 = new Message4000();
            Message5000 msg5000 = new Message5000();

            Console.WriteLine("Handler1:");

            Handler1 handler1 = new Handler1();
            handler1.ReceiveMessage(msgBase);
            handler1.ReceiveMessage(msg1000);
            handler1.ReceiveMessage(msg2000);
            handler1.ReceiveMessage(msg3000);
            handler1.ReceiveMessage(msg4000);
            handler1.ReceiveMessage(msg5000);

            Console.WriteLine("iHandler1:");

            IHandler ihandler1 = new Handler1();
            ihandler1.ReceiveMessage(msgBase);
            ihandler1.ReceiveMessage(msg1000);
            ihandler1.ReceiveMessage(msg2000);
            ihandler1.ReceiveMessage(msg3000);
            ihandler1.ReceiveMessage(msg4000);
            ihandler1.ReceiveMessage(msg5000);

            Console.WriteLine("Handler2:");

            Handler2 handler2 = new Handler2();
            handler2.ReceiveMessage(msgBase);
            handler2.ReceiveMessage(msg1000);
            handler2.ReceiveMessage(msg2000);
            handler2.ReceiveMessage(msg3000);
            handler2.ReceiveMessage(msg4000);
            handler2.ReceiveMessage(msg5000);

            Console.WriteLine("iHandler2:");

            IHandler ihandler2 = new Handler2();
            ihandler2.ReceiveMessage(msgBase);
            ihandler2.ReceiveMessage(msg1000);
            ihandler2.ReceiveMessage(msg2000);
            ihandler2.ReceiveMessage(msg3000);
            ihandler2.ReceiveMessage(msg4000);
            ihandler2.ReceiveMessage(msg5000);

            Console.WriteLine("press any key to exit");
            Console.ReadLine();
        }
    }
}

输出实际上是:

Testing method overloads
Handler1:
Handler1.ReceiveMessage(MessageBase:MessageBase)
Handler1.ReceiveMessage(Message1000:Message1000)
Handler1.ReceiveMessage(Message2000:Message2000)
Handler1.ReceiveMessage(Message3000:Message3000)
Handler1.ReceiveMessage(Message4000:Message4000)
Handler1.ReceiveMessage(MessageBase:Message5000)
iHandler1:
Handler1.ReceiveMessage(MessageBase:MessageBase)
Handler1.ReceiveMessage(MessageBase:Message1000)
Handler1.ReceiveMessage(MessageBase:Message2000)
Handler1.ReceiveMessage(MessageBase:Message3000)
Handler1.ReceiveMessage(MessageBase:Message4000)
Handler1.ReceiveMessage(MessageBase:Message5000)
Handler2:
Handler2.ReceiveMessage(MessageBase:MessageBase) > Handler2.HandleMessage(MessageBase:MessageBase)
Handler2.ReceiveMessage(MessageBase:Message1000) > Handler2.HandleMessage(MessageBase:Message1000)
Handler2.ReceiveMessage(MessageBase:Message2000) > Handler2.HandleMessage(MessageBase:Message2000)
Handler2.ReceiveMessage(MessageBase:Message3000) > Handler2.HandleMessage(MessageBase:Message3000)
Handler2.ReceiveMessage(MessageBase:Message4000) > Handler2.HandleMessage(MessageBase:Message4000)
Handler2.ReceiveMessage(MessageBase:Message5000) > Handler2.HandleMessage(MessageBase:Message5000)
iHandler2:
Handler2.ReceiveMessage(MessageBase:MessageBase) > Handler2.HandleMessage(MessageBase:MessageBase)
Handler2.ReceiveMessage(MessageBase:Message1000) > Handler2.HandleMessage(MessageBase:Message1000)
Handler2.ReceiveMessage(MessageBase:Message2000) > Handler2.HandleMessage(MessageBase:Message2000)
Handler2.ReceiveMessage(MessageBase:Message3000) > Handler2.HandleMessage(MessageBase:Message3000)
Handler2.ReceiveMessage(MessageBase:Message4000) > Handler2.HandleMessage(MessageBase:Message4000)
Handler2.ReceiveMessage(MessageBase:Message5000) > Handler2.HandleMessage(MessageBase:Message5000)
press any key to exit

处理程序 1 在直接调用时实际上可以工作。不幸的是,作为接口调用时不是开箱即用的。

虽然我认为至少带有受保护重载的 Handler2 可以解决问题....


我实际上想要摆脱的是在 Handler3 中带有强制转换的 switch 语句(因为很容易错过额外的步骤,并且在开发人员无法访问的基类中实现这种魔力会很棒):

public class Handler3 : IHandler
{
    public void ReceiveMessage(MessageBase msg)
    {
        Console.Write($"Handler3.ReceiveMessage(MessageBase:{msg.Value}) > ");
        
        switch (msg)
        {
            case Message1000 msg1000: HandleMessage(msg1000); break;
            case Message2000 msg2000: HandleMessage(msg2000); break;
            case Message3000 msg3000: HandleMessage(msg3000); break;
            case Message4000 msg4000: HandleMessage(msg4000); break;
            default: Console.WriteLine("dropped because not supported: " + msg.Value); break; // for the msg5000
        }
    }
    //protected void HandleMessage(MessageBase msg) { Console.WriteLine($"Handler3.HandleMessage(MessageBase:{msg.Value})"); }
    protected void HandleMessage(Message1000 msg) { Console.WriteLine($"Handler3.HandleMessage(Message1000:{msg.Value})"); }
    protected void HandleMessage(Message2000 msg) { Console.WriteLine($"Handler3.HandleMessage(Message2000:{msg.Value})"); }
    protected void HandleMessage(Message3000 msg) { Console.WriteLine($"Handler3.HandleMessage(Message3000:{msg.Value})"); }
    protected void HandleMessage(Message4000 msg) { Console.WriteLine($"Handler3.HandleMessage(Message4000:{msg.Value})"); }
    // intentionally no overload for Message5000
}

实际效果如输出所示:

Handler3:
Handler3.ReceiveMessage(MessageBase:MessageBase) > dropped because not supported: MessageBase
Handler3.ReceiveMessage(MessageBase:Message1000) > Handler3.HandleMessage(Message1000:Message1000)
Handler3.ReceiveMessage(MessageBase:Message2000) > Handler3.HandleMessage(Message2000:Message2000)
Handler3.ReceiveMessage(MessageBase:Message3000) > Handler3.HandleMessage(Message3000:Message3000)
Handler3.ReceiveMessage(MessageBase:Message4000) > Handler3.HandleMessage(Message4000:Message4000)
Handler3.ReceiveMessage(MessageBase:Message5000) > dropped because not supported: Message5000

但如果您错过使用该变体的方法重载,编译器会抱怨并阻止构建,这是一个好处。

【问题讨论】:

    标签: c# overloading


    【解决方案1】:

    嗯,是的。请记住,重载解析是在编译时使用变量的编译时类型完成的,而不是在运行时完成的。运行时类型无关紧要。

    IHandler 只有重载void ReceiveMessage(MessageBase msg)。所以当你调用IHandler.ReceiveMessage(msg)时,无论msg是什么子类,它都必须调用IHandler.ReceiveMessage(MessageBase msg),因为这是IHandler定义的唯一方法。

    Handler1 定义了其他不在IHandler 中的方法并不重要:您的Main 方法正在使用IHandler 的实例,因此void ReceiveMessage(MessageBase msg) 是它唯一的重载可以看到。

    Handler2.ReceiveMessage(MessageBase msg) 中,msg 具有编译时类型MessageBase。您可以在方法签名中看到它。所以当你调用HandleMessage(msg)时,msgMessageBase,所以编译器必须选择HandleMessage(MessageBase msg)重载。


    实现您所追求的一种可能方法是使用visitor pattern。这使您可以获取编译时类型为MessageBase 的变量,并通过要求它调用您的特定方法来找出它的运行时类型。比如:

    public interface IMessageVisitor
    {
        void Accept(Message1000 msg);
        void Accept(Message2000 msg);
    }
    
    // for the example: we are sending messages
    // which have a common base class
    public abstract class MessageBase
    {
        public readonly string Value;
        public MessageBase() { Value = GetType().Name; }
        public abstract void Visit(IMessageVisitor visitor);
    }
    
    // and there are a couple of concrete instances 
    public class Message1000 : MessageBase
    {
        public override void Visit(IMessageVisitor visitor) => visitor.Accept(this);
    }
    public class Message2000 : MessageBase
    {
        public override void Visit(IMessageVisitor visitor) => visitor.Accept(this);
    }
    
    public interface IHandler
    {
        void ReceiveMessage(MessageBase msg);
    }
    
    public class Handler1 : IHandler, IMessageVisitor
    {
        public void ReceiveMessage(MessageBase msg) => msg.Visit(this);
        
        public void Accept(Message1000 msg) => Console.WriteLine("Message1000");
        public void Accept(Message2000 msg) => Console.WriteLine("Message2000");
    }
    

    dotnetfiddle.net 上查看。

    【讨论】:

      【解决方案2】:

      方法是根据对象的类型而不是参数的类型来解析的。 IE。您将根据处理程序对象的类型而不是消息对象调用Handler1.ReceiveMessageHandler2.ReceiveMessage。这在技术上称为"single dispatch"

      您想要的是“多次调度”,即您希望方法基于两个不同的对象来解析。

      一种方法是将接口更改为抽象基类,并使用模式匹配将类型映射到正确的方法

      public abstract  class HandlerBase
      {
          protected abstract void ReceiveMessage(Message1000 msg);
          protected abstract void ReceiveMessage(Message2000 msg);
           // etc
          void ReceiveMessage(MessageBase msg){
               switch(msg){
                  case Message1000  msg1000: 
                      ReceiveMessage(msg1000);
                      break;
                  case Message2000  msg2000: 
                      ReceiveMessage(msg2000);
                      break;
                  // etc
               }
          }                 
      }
      

      另一种选择是visitor pattern

      public abstract class MessageBase
      {
          public readonly string Value;
          public MessageBase() { Value = GetType().Name; }
          public abstract void Visit(IVisitor visitor);
      }
      public class Message1000
      {
          public override void Visit(IVisitor visitor) => visitor.ReceiveMessage(this);
      }
      public interface IVisitor{
          void ReceiveMessage(Message1000 msg);
          void ReceiveMessage(Message2000 msg);
          // etc...
      }
      

      访问者模式将强制你实现所有需要的方法,一个新的消息类型必须有一个 Accept 方法,并且要实现这个你需要一个新的 ReceiveMessage 重载,并且必须实现由所有访客/处理程序。这是否有益取决于您。

      第三种选择是使用"dynamic",但我不推荐它,因为它会禁用所有类型检查。

      【讨论】:

        猜你喜欢
        • 2013-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多