简体   繁体   English

c# 7.0:打开 System.Type

[英]c# 7.0: switch on System.Type

No existing question has an answer to this question.现有的问题没有这个问题的答案。

In c# 7, can I switch directly on a System.Type ?在 c# 7 中,我可以直接打开System.Type吗?

When I try:当我尝试:

    switch (Type)
    {
      case typeof(int):
        break;
    }

it tells me that typeof(int) needs to be a constant expression.它告诉我typeof(int)需要是一个常量表达式。

Is there some syntatic sugar that allows me to avoid case nameof(int): and directly compare the types for equality?是否有一些语法糖可以让我避免case nameof(int):并直接比较类型是否相等? nameof(T) in a case statement is not completely good because namespaces. case 语句中的nameof(T)并不完全好,因为命名空间。 So although name collision is probably not be applicable for int , it will be applicable for other comparisons.因此,尽管名称冲突可能不适用于int ,但它将适用于其他比较。

In other words, I'm trying to be more type-safe than this:换句话说,我试图比这更安全:

    switch (Type.Name)
    {
      case nameof(Int32):
      case nameof(Decimal):
        this.value = Math.Max(Math.Min(0, Maximum), Minimum); // enforce minimum 
        break;
    }

The (already linked) new pattern matching feature allows this. (已链接的)新模式匹配功能允许这样做。

Ordinarily, you'd switch on a value:通常,您会打开一个值:

switch (this.value) {
  case int intValue:
    this.value = Math.Max(Math.Min(intValue, Maximum), Minimum);
    break;
  case decimal decimalValue:
    this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
    break;
}

But you can use it to switch on a type, if all you have is a type:但是如果你只有一个类型,你可以用它来打开一个类型:

switch (type) {
  case Type intType when intType == typeof(int):
  case Type decimalType when decimalType == typeof(decimal):
    this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
    break;
}

Note that this is not what the feature is intended for, it becomes less readable than a traditional if ... else if ... else if ... else chain, and the traditional chain is what it compiles to anyway.请注意,这不是该功能的用途,它比传统的if ... else if ... else if ... else链更具可读性,而传统的链无论如何都是它编译成的。 I do not recommend using pattern matching like this.我不建议使用这样的模式匹配。

The issue raised here by the OP is that you can't use the new C# 7 type-based switch feature when you don't have an actual instance of the switched-upon type available, and you instead have only have its putative System.Type . OP 在这里提出的问题是,当您没有可用的已开启类型的实际实例时,您不能使用新的C# 7基于类型的开关功能,而您只有其假定的System.Type The accepted answer , summarized as follows, works well for exact type matching (minor improvement shown here, but see my final example below for yet further streamlining)...接受的答案,总结如下,适用于精确类型匹配(这里显示了微小的改进,但请参阅下面的最后一个示例以进一步简化)......

Type type = ...
switch (type)
{
    case Type _ when type == typeof(Int32):
    case Type _ when type == typeof(Decimal):
        this.value = Math.Max(Math.Min(this.value, Maximum), Minimum);
        break;
}

The important point to note is that for derived reference type hierarchies, this will not exhibit the same behavior as an if... else chain which uses the is keyword for matching.需要注意的重要一点是,对于派生的引用类型层次结构,这不会表现出与使用is关键字进行匹配的if... else链相同的行为。 Consider:考虑:

class TBase { }
class TDerived1 : TBase { }
class TDerived2 : TBase { }
class TDerived3 : TDerived2 { }

TBase inst = ...

if (inst is TDerived1)
{
    // Handles case TDerived1
}
else if (inst is TDerived2)
{
    // Handles cases TDerived2 and TDerived3
}
else if (inst is TDerived3)
{
    // NOT EXECUTED                            <---  !
}

Since TDerived3 "is-a" TDerived2 , both cases are handled by the earlier condition when using is matching.由于TDerived3 "is-a" TDerived2 ,当使用is匹配时,这两种情况都由前面的条件处理。 This highlights the difference at runtime between 'strict' or 'exact' type equality versus the more nuanced notion of type subsumption (type "compatibility").这突出了运行时“严格”或“精确”类型相等与更细微的类型包含概念(类型“兼容性”)之间的差异。 Because the types in the OP's question were ValueType primitives (which can't be derived-from), the difference couldn't matter.因为 OP 问题中的类型是ValueType原语(不能派生自),所以差异无关紧要。 But if we adapt the 'exact type matching' of the accepted answer with the example classes shown above, we will get a different result:但是,如果我们使用上面显示的示例类调整已接受答案的“精确类型匹配”,我们得到不同的结果:

Type type = ...

switch (type)
{
    case Type _ when type == typeof(TDerived1):
        // Handles case TDerived1
        break;

    case Type _ when type == typeof(TDerived2):
        // Handles case TDerived2
        break;

    case Type _ when type == typeof(TDerived3):
        // Handles case TDerived3              <---  !
        break;
}

In fact, C# 7 won't even compile a switch statement which corresponds to the if / else sequence shown earlier.事实上, C# 7甚至不会编译对应于前面显示的if / else序列的switch语句。 ( nb It seems like the compiler should detect this as a warning , rather than an error , since the harmless result is just a branch of inaccessible code--a condition which the compiler deems a warning elsewhere--and also considering that the compiler doesn't even detect, at all, the seemingly identical situation in the if / else version). :编译器似乎应该将其检测为警告,而不是错误,因为无害的结果只是无法访问代码的一个分支——编译器在其他地方认为是警告的情况——并且还考虑到编译器没有甚至根本没有检测到if / else版本中看似相同的情况)。 Here's that:就是这样:

在此处输入图像描述

In any case, which one of the alternate behaviors is appropriate, or if it even matters, will depend on your application, so my point here is just to draw attention to the distinction.在任何情况下,哪一种替代行为是合适的,或者是否重要,将取决于您的应用程序,所以我在这里的意思只是提请注意区别。 If you determine that you need the more savvy type-compatibility version of the switch approach, here is how to do it:如果您确定您需要更精明的类型兼容性版本的 switch 方法,请执行以下操作:

Type type = ...

switch (type)
{
    case Type _ when typeof(TDerived1).IsAssignableFrom(type):
        // Handles case TDerived1
        break;

    case Type _ when typeof(TDerived2).IsAssignableFrom(type):
        // Handles cases TDerived2 and TDerived3
        break;

    case Type _ when typeof(TDerived3).IsAssignableFrom(type):
        // NOT EXECUTED                       <-- !
        break;
}

Finally, as I mentioned in another answer on this page, you can simplify this usage of the switch statement even further.最后,正如我在本页的另一个答案中提到的,您可以进一步简化switch语句的这种用法。 Since we're only using the when clause functionality, and since we presumably still have the original switched-upon Type instance available in a variable, there's no need to mention that variable in the switch statement, nor repeat its Type ( Type , in this case) in each case .由于我们只使用了when子句功能,并且我们可能仍然在变量中保留了原始的已打开Type实例,因此无需在switch语句中提及该变量,也无需重复其 Type ( Type ,在此案例)在每种case Just do the following instead:只需执行以下操作:

Type type = ...

switch (true)
{
    case true when typeof(TDerived1).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived2).IsAssignableFrom(type):
        break;

    case true when typeof(TDerived3).IsAssignableFrom(type):
        break;
}

Notice the switch(true) and case(true) .注意switch(true)case(true) I recommend this simpler technique whenever you are relying only on the when clause (that is, beyond just the situation of switching on System.Type as discussed here).每当您仅依赖when子句时(也就是说,不只是在此处讨论的打开System.Type的情况),我都推荐这种更简单的技术。

Starting with Paulustrious's idea of switching on a constant, but striving for the most readability:从 Paulustrious 开启常量的想法开始,但力求获得最大的可读性:

  Type type = GetMyType();
  switch (true)
  {
    case bool _ when type == typeof(int):
      break;
    case bool _ when type == typeof(double):
      break;
    case bool _ when type == typeof(string):
      break;
    default:
      break;
  }

What's readable is subjective.可读性是主观的。 I used to do something similar in VB a long time ago so I got used to this form (but in VB the bool _ was not needed so it wasn't there).很久以前我曾经在 VB 中做过类似的事情,所以我习惯了这种形式(但在 VB 中不需要bool _所以它不存在)。 Unfortunately in c# the bool _ required.不幸的是,在 c# 中需要bool _ I'm using c# 7.0 and I think switching on a constant may not be supported in earlier compilers but I am not sure about that, so try it if you want to.我正在使用 c# 7.0,我认为在早期的编译器中可能不支持打开常量,但我不确定,所以如果你愿意,可以试试。 I think it's kindof amusing that the S/O code formatter doesn't know about when yet.我认为 S/O 代码格式化程序还不知道什么when有点有趣。

You wouldn't want to do this of course if you need the case variable, like for subclasses.如果您需要case变量,例如子类,您当然不想这样做。

But for arbitrary boolean expressions it is more suited, for example:但对于任意布尔表达式,它更适合,例如:

  switch (true)
  {
    case bool _ when extruder.Temperature < 200:
      HeatUpExtruder();
      break;
    case bool _ when bed.Temperature < 60:
      HeatUpBed();
      break;
    case bool _ when bed.Y < 0 || bed.Y > 300:
      HomeYAxis();
      break;
    default:
      StartPrintJob();
      break;
  }

Some will argue this is worse than if..else.有些人会争辩说这比 if..else 更糟糕。 The only thing I can say is switch forces one path and it's impossible to break the switch statement itself, but it is possible to leave out an else and break an if..else into multiple statements unintentionally, possibly executing two "branches" accidentally.我唯一能说的是switch强制一条路径,并且不可能破坏switch语句本身,但是可以省略一个else并无意中将 if..else 分解为多个语句,可能会意外执行两个“分支”。

Switching on a Type is really just an arbitrary switch because what we are really switching on is a property of the variable.打开一个Type实际上只是一个任意的开关,因为我们真正打开的是变量的一个属性。 Unless and until we can do case typeof(int) ( case on something that is not a constant expression), we are stuck with something akin to this if we don't want to use string constants, which in the case of type names, are not in the framework.除非并且直到我们可以做case typeof(int) (在不是常量表达式的东西上使用case ),如果我们不想使用字符串常量,我们就会遇到类似的事情,在类型名称的情况下,不在框架内。

Although the question is on C# 7 but for those with a C# 8 version or higher, you can use the newer syntax which is shorter and I think is nicer as well.虽然问题出在C# 7上,但对于C# 8或更高版本的用户,您可以使用更新的语法,它更短,我认为也更好。

type switch
{
    Type _ when type == typeof(int) || type == typeof(decimalType) =>
        this.value = Math.Max(Math.Min(this.value, Maximum), Minimum),
    _ => // default case
};

Another elegant option:另一个优雅的选择:

Type.GetTypeCode(type) switch
{
    TypeCode.Int32 or TypeCode.Decimal =>
        this.value = Math.Max(Math.Min(this.value, Maximum), Minimum),
    _ => // default case
};

For more info: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression欲了解更多信息: https ://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression

@toddmo suggested the following: @toddmo 提出以下建议:

switch (true)
{
    case bool _ when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc..
    default:
        StartPrintJob();
        break;
}

...but why not go even further in his pursuit of simplicity. ...但为什么不在他追求简单的道路上走得更远。 The following works just as well without needing the bool type qualification, nor the extraneous _ dummy variable:以下工作同样有效,不需要bool类型限定,也不需要无关的_虚拟变量:

switch (true)
{
    case true when extruder.Temperature < 200:
        HeatUpExtruder();
        break;

    // etc.
    default:
        StartPrintJob();
        break;
}

I found a simple and efficient way.我找到了一种简单有效的方法。 It requires C# V7 to run.它需要 C# V7 才能运行。 The switch("") means all cases will be satisfied up to the when clause. switch("")表示所有情况都将满足when子句。 It uses the when clause to look at the type .它使用when子句来查看type

List<Object> parameters = new List<object>(); // needed for new Action
parameters = new List<object>
{
    new Action(()=>parameters.Count.ToString()),
    (double) 3.14159,
    (int) 42,
    "M-String theory",
    new System.Text.StringBuilder("This is a stringBuilder"),
    null,
};
string parmStrings = string.Empty;
int index = -1;
foreach (object param in parameters)
{
    index++;
    Type type = param?.GetType() ?? typeof(ArgumentNullException);
    switch ("")
    {
        case string anyName when type == typeof(Action):
            parmStrings = $"{parmStrings} {(param as Action).ToString()} ";
            break;
        case string egStringBuilder when type == typeof(System.Text.StringBuilder):
            parmStrings = $"{parmStrings} {(param as System.Text.StringBuilder)},";
            break;
        case string egInt when type == typeof(int):
            parmStrings = $"{parmStrings} {param.ToString()},";
            break;
        case string egDouble when type == typeof(double):
            parmStrings = $"{parmStrings} {param.ToString()},";
            break;
        case string egString when type == typeof(string):
            parmStrings = $"{parmStrings} {param},";
            break;
        case string egNull when type == typeof(ArgumentNullException):
            parmStrings  = $"{parmStrings} parameter[{index}] is null";
            break;
        default: throw new System.InvalidOperationException();
    };
} 

This leaves parmStrings containing...这使得 parmStrings 包含...

System.Action 3.14159, 42, M-String theory, This is a stringBuilder, parameter[5] is null

Adding to the above answer, if you do not need the value to be used inside the case statement, you can use _ .添加到上述答案中,如果您不需要在 case 语句中使用该值,则可以使用 _ This syntax can be used to remove an unused variable warning message.此语法可用于删除未使用的变量警告消息。

switch (this.value) {
  case int _:
    //Do something else without value.
    break;
  case decimal decimalValue:
    this.value = Math.Max(Math.Min(decimalValue, Maximum), Minimum);
    break;
}

I know it's not applicable in all cases, but maybe my example will help someone.我知道它并不适用于所有情况,但也许我的例子会对某人有所帮助。 In ASP.NET Core I implemented my custom model binder provider and I had to resolve binder type depending on model type.在 ASP.NET Core 中,我实现了我的自定义模型绑定器提供程序,我必须根据模型类型解析绑定器类型。

The initial idea (apart from the if/else block ofc but I kept thinking it could be shorter) was the switch:最初的想法(除了 if/else 块 ofc 但我一直认为它可以更短)是开关:

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        switch (context.Metadata.ModelType)
        {
            case Type _ when context.Metadata.ModelType == typeof(Model1):
                return new BinderTypeModelBinder(typeof(Binder1));
            case Type _ when context.Metadata.ModelType == typeof(Model2):
                return new BinderTypeModelBinder(typeof(Binder2));
            case Type _ when context.Metadata.ModelType == typeof(Model3):
                return new BinderTypeModelBinder(typeof(Binder3));
        }

        return null;
    }

This is the same with a dictionary:这与字典相同:

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        var bindersByType = new Dictionary<Type, Type>
        {
            {typeof(Model1),  typeof(Binder1)},
            {typeof(Model2),  typeof(Binder2)},
            {typeof(Model3),  typeof(Binder3)}
        };

        return bindersByType.TryGetValue(context.Metadata.ModelType, out Type binderType) ? new BinderTypeModelBinder(binderType) : null;
    }

Credits for the idea go to @xxbbcc who posted this in a comment to the first question这个想法的功劳归于@xxbbcc,他在对第一个问题的评论中发布了这个

Well, it probably break your code convention, but I use this approach;好吧,它可能会破坏您的代码约定,但我使用这种方法;

var result = this.Value is int intValue
? Math.Max(Math.Min(intValue, Maximum), Minimum)
: this.Value is decimal decimalValue
? Math.Max(Math.Min(decimalValue, Maximum), Minimum)
: this.Value is double doubleValue
? Math.Max(Math.Min(decimalValue, Maximum), Minimum)
: throw new Exception($"Cannot handle '{this.Value.GetType().Name}' value.");

You can use Type.IsAssignableFrom() in condition statement when it is more appropriate.您可以在更合适的情况下在条件语句中使用Type.IsAssignableFrom()

Here is an alternative which won't compile as the classes are missing:这是由于缺少类而无法编译的替代方法:

bool? runOnUI = queuedAction.RunOnUI;  // N=null, T=true F=False
bool isOnUI = Statics.CoreDispatcher.HasThreadAccess;
bool isFF = queuedAction.IsFireAndForget;   // just makes it easier to read the switch statement
if (false == queuedAction.IsFireAndForget)
    IsOtherTaskRunning = true;

/* In the case statements below the string name is something like noFF_TN
 * The compiler ignores the string name. I have used them here to represent
 * the logic in the case statement:   ( FF = FireAnd Forget, T=true, F=false, N = null, * means 'whatever')
 * 
 *      isFF_** = FireAndForget QueuedAction
 *      noFF_** = Not FireAndForget so Wait for Task to Finish (as opposed to Complete)
 *      ****_T* = We are already on the UI thread 
 *      ****_F* = We are not on the UI thread 
 *      ****_*T = Run on the UI thread.
 *      ****_*F = Do not run on UI thread
 *      ****_*N = Don't care so run on current thread
 *      
 *  The last character is a "bool?" representing RunOnUI. It has
 *  three values each of which has a different meaning */

bool isTask;
switch ("ignore")
{
    /* Run it as an Action (not Task) on current Thread   */
    case string noFF_TT when !isFF && isOnUI && runOnUI == true:
    case string isFF_TN when isFF && isOnUI && !runOnUI == null:
    case string isFF_FN when isFF && !isOnUI && runOnUI == null:
    case string isFF_TT when isFF && isOnUI && runOnUI == true:
    case string isFF_FF when isFF && !isOnUI && runOnUI == false:
        isTask = false;
        queuedAction.ActionQA(queuedAction); // run as an action, not as a Task
        break;

    /* Create a Task, Start it */

    case string isFF_TF when isFF && isOnUI == true && runOnUI == false:
    case string noFF_TN when !isFF && isOnUI == true && runOnUI == null:     // <== not sure if I should run it on UI instead
    case string noFF_TF when !isFF && isOnUI && runOnUI == false:
    case string noFF_FN when !isFF && !isOnUI && runOnUI == null:
    case string noFF_FF when !isFF && !isOnUI && runOnUI == false:
        var cancellationTokenSource = new CancellationTokenSource();
queuedAction.Canceller?.SetCancellationTokenSource(cancellationTokenSource);
        isTask = true;
        new Task
            (
                (action) => queuedAction.ActionQA(queuedAction),
                queuedAction,
                cancellationTokenSource.Token
            )
            .Start();
        break;

    /* Run on UI and don't wait */

    case string isFF_FT when isFF && !isOnUI && runOnUI == true:
        isTask = true;
        Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
        break;

    /* Run on the UI as a Task and Wait */

    case string noFF_FT when !isFF && !isOnUI && runOnUI == true:
        isTask = true;
        Statics.RunOnUI(() => queuedAction.ActionQA(queuedAction), asTaskAlways: true);
        break;

    default:
        throw new LogicException("unknown case");
}

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

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