基于泛型参数类型的切换

问题描述 投票:0回答:5

在 C# 7.1 中,以下是有效代码:

object o = new object();
switch (o)
{
    case CustomerRequestBase c:
        //do something
        break;
}

但是,我想在以下场景中使用pattern switch语句:

public T Process<T>(object message, IMessageFormatter messageFormatter) 
    where T : class, IStandardMessageModel, new()
{
    switch (T)
    {
        case CustomerRequestBase c:
            //do something
            break;
    }
}

IDE 给出错误“'T' 是一种类型,在给定上下文中无效” 有没有一种优雅的方法来打开泛型参数的类型?我在我的第一个示例中了解到您正在打开对象,第二个示例我想打开类型 T。执行此操作的最佳方法是什么?

c# generics switch-statement pattern-matching c#-7.1
5个回答
15
投票

下面是两个不同的类,分别称为FooBar。您可以使用任何这些类的一个实例作为名为 Process 的函数的参数。毕竟,您可以执行示例函数中所示的模式匹配。对于用法示例,有一个名为 Test 的函数..

public class Foo
{
    public string FooMsg { get; set; }
}

public class Bar
{
    public string BarMsg { get; set; }
}

public class Example
{
    public T Process<T>(T procClass) where T : class
    {
        switch (typeof(T))
        {
            case
                var cls when cls == typeof(Foo):
                {
                    var temp = (Foo)((object)procClass);
                    temp.FooMsg = "This is a Foo!";

                    break;
                }

            case
                var cls when cls == typeof(Bar):
                {
                    var temp = (Bar)((object)procClass);
                    temp.BarMsg = "This is a Bar!";

                    break;
                }
        }

        return
            procClass;
    }

    public void Test(string message)
    {
        Process(new Foo() { FooMsg = message });
        Process(new Bar() { BarMsg = message });
    }
}

3
投票

我同意这种方法在某些情况下更快而且不那么丑陋,并且也同意无论如何都应该找到更好的解决方案,但有时权衡不值得......所以这是一个解决方案( C# 9.0)

return typeof(T) switch
{
    Type t when t == typeof(CustomerRequestBase) => /*do something*/ ,
    _ => throw new Exception("Nothing to do")
};

0
投票

如果我们根据评论忽略 codesmell 讨论,一个易于阅读的实现(hack)可能如下所示:

public T Process<T>(string number)
{   
    switch (typeof(T).FullName)
    {
        case "System.Int32":
            return (dynamic) int.Parse(number);
        case "System.Double":
            return (dynamic) double.Parse(number);
        default:
            throw new ArgumentException($"{typeof(T).FullName} is not supported");
    }
}

即使您 # 有通用约束,这也可能会导致问题,除非您是唯一的程序员 ;)


0
投票

我要在序言中说,总的来说,我同意所有评论者的观点,即打开通用 T 可能不是一个好主意。在这种情况下,我会建议他坚持识别对象,投射它,然后将它传递给适当的处理程序。

但是,我一直在编写一个需要相当高效的自定义二进制序列化程序,并且我发现了一个案例,我觉得他要求的那种切换(或 if 语句)是合理的,所以这就是我的管理方式。


public T ProcessAs<T>(parameters)
{
    if (typeof(T) == typeof(your_type_here)
    {
        your_type_here tmp = process(parameters);
        return Unsafe.As<your_type_here, T>(ref tmp);
    }
    else if (typeof(T) == typeof(your_other_type))
    {
        your_other_type tmp = otherProcess(parameters);
        return Unsafe.As<your_other_type, T>(ref tmp);
    }
    else
    {
        throw new ArgumentException(appropriate_msg);
    }
}

请注意,如果您处理的是类而不是结构,则可以使用

Unsafe.As<T>(T value)


0
投票

我使用了typeof(T), typeof(T) t when t == typeof(X), typeof(T).Name, and typeof(T).FullName variations (suggested in其他答案),但我从来没有对他们中的任何一个感到满意。它们要么太复杂,要么太慢。

typeof(T) when t == typeof(X) 可能是最好的,但是,它的性能值得怀疑,因为编译器似乎将 when 子句视为一系列 if ... else if ... 语句。运行调试器并跟踪它以了解我的意思。字符串变体也有类似的问题——获取类型名称和比较字符串似乎是不必要的开销。

我们真正想要的是一种使用本机开关哈希查找行为以获得最佳性能的解决方案。

所以这是另一个降低复杂性的解决方案:

  1. 创建一个虚拟变量 T
  2. 分配一个新值
  3. 在switch语句中使用T类型的哑变量

结果:

public T Process<T>(object message, IMessageFormatter messageFormatter) 
    where T : class, IStandardMessageModel, new()
{
    T dummy = Activator.CreateInstance(typeof(T));
    
    switch (dummy)
    {
        case CustomerRequestBase _:
            //do something
            break;
    }
}

T 需要一个构造函数,在这种情况下它是完美的,因为该方法是用 where T: class, new() 限定的——是一个类并且有一个默认的构造函数。所以我们可以实例化虚拟变量,并使用该虚拟变量来执行标准的 switch 变量 case 类型功能。

警告:T dummy = default 不会起作用,因为默认值通常为 Null,我们不能在 switch 语句中使用 Null。这就是使用 Activator.CreateInstance() 的原因。

case语句中的丢弃(_)用于阻止使用虚拟变量。据推测,case 语句将具有处理“消息”的其他逻辑。

© www.soinside.com 2019 - 2024. All rights reserved.