简体   繁体   English

C# 方法重载和接口

[英]C# method overloading and interfaces

Scrumbling my head around basics.在基础知识上乱七八糟。

I have a couple of (for the example let's say) message classes sharing a common base class.我有几个(例如)消息类共享一个公共基础 class。 And I have an interface that accepts the base class as argument.我有一个接受基本 class 作为参数的接口。 So far I thought it would easily be possible to use method overloading to handle specific types of messages in seperated methods.到目前为止,我认为可以很容易地使用方法重载来处理单独方法中的特定类型的消息。

How could you get this following sample running:您如何运行以下示例:

    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();
        }
    }
}

The output actually is: output实际上是:

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 actually works when called directly.直接调用时,处理程序 1 实际工作。 Unfortunatly not out of the box when called as interface.不幸的是,当作为接口调用时不是开箱即用的。

Though I thought at least Handler2 with the protected overloads would do the trick....虽然我认为至少具有受保护重载的 Handler2 可以解决问题....


What I am actually trying to get rid of is the switch statement with castings in Handler3 (because that extra step could easily be missed and it would be great to do that magic in a base class unaccessible for the developers):我实际上想要摆脱的是在 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
}

Which would actually works as shown by the output:如 output 所示,这实际上可以工作:

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

But it is a benefit that the compiler complains and prevents builds if you miss an method overload using that variant.但是,如果您错过使用该变体的方法重载,编译器会抱怨并阻止构建,这是一个好处。

Well, yes.嗯,是。 Remember that overload resolution is done at compile-time using the compile-time types of your variables, and not at runtime.请记住,重载决议是在编译时使用变量的编译时类型完成的,而不是在运行时完成的。 The runtime types are irrelevant.运行时类型无关紧要。

IHandler only has the overload void ReceiveMessage(MessageBase msg) . IHandler只有重载void ReceiveMessage(MessageBase msg) So when you call IHandler.ReceiveMessage(msg) , whatever subclass msg happens to be, it has to call IHandler.ReceiveMessage(MessageBase msg) , because that's the only method which IHandler defines.因此,当您调用IHandler.ReceiveMessage(msg)时,无论msg是什么子类,它都必须调用IHandler.ReceiveMessage(MessageBase msg) ,因为这是IHandler定义的唯一方法。

It doesn't matter that Handler1 defines other other methods which aren't in IHandler : your Main method is working with an instance of IHandler , and so void ReceiveMessage(MessageBase msg) is the only overload it can see. Handler1定义不在IHandler中的其他其他方法并不重要:您的Main方法正在使用IHandler的实例,因此void ReceiveMessage(MessageBase msg)是它可以看到的唯一重载。

In Handler2.ReceiveMessage(MessageBase msg) , msg has the compile-time type MessageBase .Handler2.ReceiveMessage(MessageBase msg)中, msg具有编译时类型MessageBase You can see it in the method signature.您可以在方法签名中看到它。 So when you call HandleMessage(msg) , msg is a MessageBase , so the compiler has to pick the HandleMessage(MessageBase msg) overload.因此,当您调用HandleMessage(msg)时, msgMessageBase ,因此编译器必须选择HandleMessage(MessageBase msg)重载。


One possible way to achieve what you're after is to use the visitor pattern .实现您所追求的一种可能方法是使用访问者模式 This lets you take a variable with a compile-time type of MessageBase , and find out what its run-time type is by asking it to call a specific method on you.这使您可以获取具有编译时类型MessageBase的变量,并通过要求它调用您的特定方法来找出它的运行时类型。 Something like:就像是:

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");
}

See it on dotnetfiddle.net .dotnetfiddle.net上查看。

Methods are resolved based on type of the object, not the type of the parameter.方法是根据 object 的类型而不是参数的类型来解析的。 Ie you will call Handler1.ReceiveMessage or Handler2.ReceiveMessage based on the type of the handler object, not the message object.即,您将根据处理程序 object 的类型而不是消息 object 调用Handler1.ReceiveMessageHandler2.ReceiveMessage This is technically known as "single dispatch"这在技术上称为“单次调度”

What you want is "multiple dispatch", ie you want the method to be resolved based on two different objects.您想要的是“多次调度”,即您希望该方法基于两个不同的对象进行解析。

One way to do this would be to change your interface to an abstract base class and use pattern matching to map the type to the correct method一种方法是将您的接口更改为抽象基础 class 并使用与 map 类型匹配的模式到正确的方法

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
         }
    }                 
}

Another alternative is the 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...
}

The visitor pattern will force you to implement all the needed methods, a new message type must have an Accept method, and to implement this you need a new ReceiveMessage overload, and that has to be implemented by all the visitors/handlers.访问者模式将强制你实现所有需要的方法,一个新的消息类型必须有一个 Accept 方法,并且要实现这个你需要一个新的 ReceiveMessage 重载,并且必须由所有访问者/处理程序实现。 If this is a benefit or not is up to you.这是否有好处取决于您。

A third alternative is to use "dynamic" , but I would not recommend it since it will disable all type checking.第三种选择是使用"dynamic" ,但我不推荐它,因为它会禁用所有类型检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM