C# 方法重载和接口

C# method overloading and interfaces

在基础知识上摸不着头脑。

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

您如何获得以下示例 运行:

    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

Handler 1 在直接调用时实际工作。不幸的是,作为接口调用时不是开箱即用的。

尽管我认为至少具有受保护重载的 Handler2 可以解决问题....


我实际上想要摆脱的是 Handler3 中带有转换的 switch 语句(因为这个额外的步骤很容易被遗漏,并且在一个基础 class 无法访问的基础上做这个魔术会很棒开发人员):

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

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

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

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 上查看。

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

你想要的是“多重分派”,即你希望基于两个不同的对象来解析方法。

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

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 重载,并且必须由所有 visitors/handlers 实施。这是否有好处取决于您。

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