【问题标题】:Calling non-generic method from generic method in C#从 C# 中的泛型方法调用非泛型方法
【发布时间】:2021-09-17 21:15:34
【问题描述】:

这和这个问题Call overloaded generic method from generic method类似,但不一样:在那种情况下,方法返回void,而我需要返回泛型类型T,所以建议的解决方案不起作用。

我正在实现一个类似元组的类,它具有通用参数和这些参数的访问器:

public class MyClass<T1, T2>
{
    private readonly T1 _item1;
    private readonly T2 _item2;

    public MyClass(T1 item1, T2 item2)
    {
        _item1 = item1;
        _item2 = item2;
    }

    public T1 GetItem(T1 _) => _item1;

    public T2 GetItem(T2 _) => _item2;

    private T GetItem<T>(T x)
    {
        throw new ArgumentException();
        // return x;
    }

    public T Get<T>()
    {
        return GetItem(default(T));
    }
}

我想这样使用这个类:

var obj = new MyClass<int, string>(1, "hello");
var a = obj.Get<int>();
var b = obj.Get<string>();

但是如果我尝试这个,Generic T GetItem&lt;T&gt;(T x) 总是被调用(抛出异常),而不是非泛型实现。

如果我尝试直接访问GetItem 方法,它会按预期工作:

var obj = new MyClass<int, string>(1, "hello");
var q = obj.GetItem(default(int));
var w = obj.GetItem(default(string));

qw 按预期包含 1hello

有没有办法让我随心所欲地使用这个类(使用obj.Get&lt;T&gt;)?我想避免Item1 Item2 Item3 getters。

【问题讨论】:

  • 您能否说明您是如何尝试使用该帖子中的答案的?
  • 你的代码有很大缺陷。您有两个相同的方法GetItem,它们根据唯一目的是区分调用的参数返回不同的东西。您应该为此使用两个不同的名称,例如GetItem1GetItem2.
  • 重载解析是在编译时完成的,而不是在运行时。当编译器编译Get&lt;T&gt; 方法时,它必须立即决定要调用GetItem 的哪个重载。当然,此时编译器不知道T 将是什么:它可能是stringint 或其他任何东西。所以它必须选择通用的GetItem&lt;T&gt;重载
  • 当你想要一个包含两个相同类型项目的元组时,你将陷入痛苦的世界......
  • @Sweeper 如果我将动态与泛型一起使用,那么我总是必须将数据参数作为参数传递(我的第二个示例),或者调用泛型 T (我的第一个示例)。如果我不使用泛型,那么......好吧,我不能使用违背目的的泛型。在这种情况下,dynamic 没有任何效果。 @Matthew 具有相同类型的两个项目破坏了业务规则。在这种情况下,UB 是可以接受的。

标签: c# generics


【解决方案1】:

但如果我尝试这样做,则始终调用泛型 T GetItem&lt;T&gt;(T x)(抛出异常),而不是非泛型实现。

这是因为重载决议发生在编译时,而在编译时,T 的具体类型是未知的。

有没有办法让我随心所欲地使用这个类(使用obj.Get&lt;T&gt;)?

使用普通的旧动态类型检查,当然:

public class MyClass<T1, T2>
{
    private readonly T1 _item1;
    private readonly T2 _item2;

    public MyClass(T1 item1, T2 item2)
    {
        _item1 = item1;
        _item2 = item2;
    }

    public T Get<T>()
    {
        if (typeof(T) == typeof(T1))
        {
            return (T)(object)_item1;
        } 
        
        if (typeof(T) == typeof(T2))
        {
            return (T)(object)_item2;
        }
        
        throw new InvalidOperationException();
    }
}

此解决方案的优点是边缘情况(T1 == T2,或 T 既不匹配 T1 也不匹配 T2)的行为被正确定义且易于查看。


或者,您可以使用您链接到的问题中使用的dynamic“hack”,您只需将结果转换为Treturn (T)GetItem((dynamic)default(T));,这是@987654321 @。

但这太丑了,请不要那样做。将 fiddle 与上面的版本进行比较,并诚实地判断哪一个更易于阅读、更易于理解,因此更易于维护。

【讨论】:

    【解决方案2】:

    测试

    var instance = new MyClass<string, int>("hello", 1);
    Console.WriteLine(instance.GetItem("hi"));
    Console.WriteLine(instance.GetItem(2));
    // returns "hello"
    Console.WriteLine(instance.Get<string>());
    // returns "1"
    Console.WriteLine(instance.Get<int>());
    // throws with details
    Console.WriteLine(instance.Get<object>());
    

    实施

    public T Get<T>()
    {
        var parentType = typeof(MyClass<T1, T2>);
        var method = parentType.GetMethod(nameof(GetItem), new[] { typeof(T) });
        if (method == null)
            throw new NotImplementedException(
                $"No implementation for {nameof(GetItem)} with parameter type {typeof(T).FullName}");
    
        var result = (T)method.Invoke(this, new object[] { default(T) });
        return result;
    }
    

    这个版本使用反射。如果您需要更高的性能,您应该使用已编译的表达式,但这更难维护

    【讨论】:

    • 是的,我不想在每个 Get 上调用反射。您有什么建议可以让我阅读有关编译表达式的信息吗?
    • @fnzr google "c#compiled expression" 上面有很多文章。除非您仔细阅读,否则语法非常混乱。这些天反射不是那么慢而且很容易理解,所以除非你的用例对性能非常关键,否则我不会走那条路
    【解决方案3】:

    如果这两个项目具有相同的数据类型怎么办?

    var obj = new MyClass<int, int>(1, 2);
    

    GetItem 应该在这里返回什么?显然没有有效的答案。

    无论如何提供一个参数只是为了区分调用哪个方法是很奇怪的。实际上,您的 GetItem&lt;T&gt;-method 根本不应该是通用的。只需使用两种不同的方法:

    T1 GetItem1() => _item1;
    T2 GetItem2() => _item2;
    

    或使用自动属性摆脱支持字段:

    public class MyClass<T1, T2>
    {
        public T1 Item1 { get; }
        public T2 Item2 { get; }
    
        public MyClass(T1 item1, T2 item2)
        {
            Item1 = item1;
            Item2 = item2;
        }
    

    【讨论】:

      猜你喜欢
      • 2011-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多