V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wdc63
V2EX  ›  C#

我应该如何用 C#方法重载实现这样的目的

  •  
  •   wdc63 · 2022-08-06 02:44:35 +08:00 · 2020 次点击
    这是一个创建于 832 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我有一系列类型不定的对象,同时为每个对象提供了相应的方法,这个对象由一个方法在实时运算中提供,不确定,请问 C#中有没有方法如何利用重载或其他手段来实现对象被输入给相应的方法,例如实现下面这种,但是当然下面这种写法会报错。

    不太想用判断,写起来太繁琐了。

        private static void test(string str)
        {
            Console.WriteLine(str);
        }
    
        private static void test(int i)
        {
            Console.WriteLine(i);
        }
    
        private static void Main(string[] args)
        {
            var a = new List<object>() { 123, "asd" };
            test(a[0]);
            test(a[1]);
        }
    
    23 条回复    2022-08-09 00:24:43 +08:00
    wudicgi
        1
    wudicgi  
       2022-08-06 02:51:55 +08:00
    wudicgi
        2
    wudicgi  
       2022-08-06 02:53:05 +08:00
    如果类型不多的话,还是直接判断和调用比较好
    wdc63
        3
    wdc63  
    OP
       2022-08-06 03:00:23 +08:00
    @wudicgi 谢谢,反射的性能开销大不大呢,会有大量的对象在循环调用这些方法。
    而且类型太多了,几十种,如果用判断,那方法写起来太繁琐了。
    wewewefff
        4
    wewewefff  
       2022-08-06 03:03:56 +08:00
    突然想到热重载,有人解释一下吗
    wudicgi
        5
    wudicgi  
       2022-08-06 03:11:32 +08:00
    @wdc63 一定要靠参数类型以类似方法重载的方式调用吗?
    每个调用如果事先存储好方法名,或者可以查到方法名,就可以直接调用了

    运行时靠参数类型去找方法的重载有点奇怪
    wdc63
        6
    wdc63  
    OP
       2022-08-06 03:18:55 +08:00
    @wudicgi 我能想到的就是靠参数类型去启动相应的方法,大概就是这么个意思:一个对象在一个场景中走,遇到 A 对象就和 A 对象产生互动,遇到 B 对象就和 B 对象互动。请问对于这种需求有没有更好的实现方法呢?
    wdc63
        7
    wdc63  
    OP
       2022-08-06 03:21:16 +08:00
    @wudicgi 另外 A 、B 对象的行为差距很大的,有几十个这种对象类,没法用一个接口来实现。
    geelaw
        8
    geelaw  
       2022-08-06 03:29:43 +08:00 via iPhone
    你要找的是不是 visitor pattern ?

    另外你的例子不好,因为内置类型是无法修改,但如果要考虑的类型是你的代码所控制的,则可以修改。

    最后,如果按照你最开始的问题回答,你可以用 dynamic 。
    dcsuibian
        9
    dcsuibian  
       2022-08-06 03:37:19 +08:00
    重载使用的方法好像是编译时就确定的吧。
    Aloento
        10
    Aloento  
       2022-08-06 03:53:47 +08:00
    不想用 switch + match 的话(其实这个才是最好的方法)
    就只能用反射了,反射开销极大
    catcn
        11
    catcn  
       2022-08-06 08:37:15 +08:00
    用泛型,抽取 interface ,用 interface 限制泛型
    nebkad
        12
    nebkad  
       2022-08-06 08:54:39 +08:00 via Android
    需求没有表达得很清楚,我不是很理解你想做什么。不过看起来你只需要结合 delegate 就完事了,用参数类型作为 key 把一堆 delegate 保存在一个 dict 里面,然后实际作运行的时候检查 list 里面的每个元素的类型来查出 delegate
    wdc63
        13
    wdc63  
    OP
       2022-08-06 15:36:43 +08:00
    @nebkad 谢谢,请问您能不能举个例子呢?
    wdc63
        14
    wdc63  
    OP
       2022-08-06 15:37:00 +08:00
    @catcn 不太懂这种方法。
    wdc63
        15
    wdc63  
    OP
       2022-08-06 15:37:36 +08:00
    @nebkad 需求就是一个对象在运行过程中会随机遇到另一个对象,然后触发相应的方法。
    wdc63
        16
    wdc63  
    OP
       2022-08-06 15:39:31 +08:00
    @geelaw 看了 visitor pattern ,应该可行,但是感觉有点复杂了,比 switch 复杂得多,如果能用反射或者后面 delegate 的方法实现就好了。
    wdc63
        17
    wdc63  
    OP
       2022-08-06 15:40:09 +08:00
    @Aloento 谢谢,反射为什么开销很大呢。
    Al0rid4l
        18
    Al0rid4l  
       2022-08-06 17:28:47 +08:00
    ```C#
    public class Program {
    public static void Test(object param) {
    Console.WriteLine(
    param switch {
    int a => a,
    string b => b,
    _ => throw new ArgumentException("error")
    }
    );
    }

    public static void Main() {
    var arr = new List<object> { "abc", 123 };
    foreach (object item in arr) {
    Test(item);
    }
    }
    }
    ```

    是要这样的?
    Aloento
        19
    Aloento  
       2022-08-06 18:48:47 +08:00
    @wdc63 反射为什么开销大...你每次调用的时候都要 VM 在整个 Class 里面查询匹配,然后动态生成代码,再执行,步骤太多了开销自然大
    Zhuzhuchenyan
        20
    Zhuzhuchenyan  
       2022-08-07 03:20:02 +08:00
    如果不想写繁琐的 switch ,同时也想避免反射带来的开销,考虑一下 Delegate.CreateDelegate 来创建一个可以重复使用的 Delegate

    https://gist.github.com/Charles-YYH/97785d39b72bef2df1b639a5e0081289
    我放了一个示例在这里,仓促之中写完,这个示例有不少可以优化的地方
    1. ProcessInt 和 ProcessString 应该是可以避免使用 object 来作为形参的类型,但是既然你题干中把值类型和引用类型都放在同一个 List 里,说明此处装箱应该是可以接受的
    2. 如果觉得 Dictionary 查找太慢,可以考虑使用表达式树来达到和 switch 相同数量级的的性能


    相关资料
    1. Delegate.CreateDelegate 性能研究: https://stackoverflow.com/a/16078960/8877198 ,大概是普通反射的 30 倍快
    hez2010
        21
    hez2010  
       2022-08-07 23:51:48 +08:00 via Android
    @Zhuzhuchenyan
    > 大概是普通反射的 30 倍快

    以前可能是这样,但这一点在 .NET 7 上不成立了
    neilq
        22
    neilq  
       2022-08-08 15:47:18 +08:00
    // 简单用设计模式包裹了一下,工厂+策略,没测试,可以复制到 ide 方便看

    public class HandlerKeyAttribute : Attribute
    {
    public HandlerKeyAttribute(string name)
    {
    Name = name;
    }

    public string Name { get; set; }
    }

    [HandlerKey(nameof(ArgA))]
    class ArgA
    {
    }

    [HandlerKey(nameof(ArgB))]
    class ArgB
    {
    }

    public class Message
    {
    public object Param { get; set; }
    }

    public interface IHandler
    {
    Task Handle(Message msg);
    }

    [HandlerKey(nameof(ArgA))]
    public class HandlerA : IHandler
    {
    public Task Handle(Message msg)
    {
    throw new NotImplementedException();
    }
    }

    [HandlerKey(nameof(ArgB))]
    public class HandlerB : IHandler
    {
    public Task Handle(Message msg)
    {
    throw new NotImplementedException();
    }
    }

    public class HandlerFactory
    {
    private static readonly Dictionary<string, Type> _handlerTypes = new();

    static HandlerFactory()
    {
    var handlerTypeInfos = Assembly.GetAssembly(typeof(Program)).DefinedTypes
    .Where(x => x.IsClass
    && !x.IsAbstract
    && x.GetInterfaces().Any(i => i == typeof(IHandler)))
    .ToList();
    foreach (var type in handlerTypeInfos)
    {
    var attr = type.GetCustomAttribute<HandlerKeyAttribute>();
    if (attr != null && !string.IsNullOrEmpty(attr.Name))
    {
    try
    {
    _handlerTypes.Add(attr.Name, type);
    }
    catch (Exception)
    {
    // 当心 key name 重复
    }
    }
    }
    }

    public IHandler CreateHandler(object arg)
    {
    //简单用 attribute 匹配 arg 与 handler 类型,具体场景也可以用其他特征(如直接用对象名称)匹配,也可以结合正则、startsWith 等方式匹配
    var attr = arg.GetType().GetCustomAttribute<HandlerKeyAttribute>();

    if (attr == null || string.IsNullOrEmpty(attr.Name)) return null;

    if (!_handlerTypes.TryGetValue(attr.Name, out var handlerType))
    return null;

    // 部分框架里可以利用 ioc container 创建, 如 return _serviceProvider.GetRequiredService(handlerType) as IMqttMessageHandler;
    // 部分场景也可以预创建对象,如_handlerTypes 类型改成 Dictionary<string, IHandler> handlers, 直接取拿出来用: return handlers[attr.name]
    return (IHandler)Activator.CreateInstance(handlerType);

    }
    }

    public class Program
    {
    private static void Main(string[] args)
    {
    var objects = new List<object> { new ArgA(), new ArgB() };
    var factory = new HandlerFactory();
    foreach (var arg in objects)
    {
    var handler= factory.CreateHandler(arg);
    handler.Handle(new Message { Param = arg });
    }
    }
    }
    wdc63
        23
    wdc63  
    OP
       2022-08-09 00:24:43 +08:00
    @neilq 谢谢大佬,太有帮助了,这两天我自己想着弄了个委托的方法。我再测试下您的模式。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5732 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 03:17 · PVG 11:17 · LAX 19:17 · JFK 22:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.