简体   繁体   English

C# - 切换泛型

[英]C# - switch in generics

I tried to compare a generic-typed parameter over a switch within a generic method. 我试图在泛型方法中的开关上比较泛型类型参数。 That doesn't work for me with my solution. 这对我的解决方案不起作用。 The reason: the parameter must be a specific type (bool, char, string, integral, enum). 原因是:参数必须是特定类型(bool,char,string,integral,enum)。

    public T testfunction<T, U>(U numb)
    {
        switch(numb){ //<-- error

        }
        ....
    }

But what's the sense behind it? 但它背后的意义是什么? If the parameter is generic and I want to do a comparison, why does it have to be a type defined variable? 如果参数是通用的并且我想进行比较,为什么它必须是类型定义的变量?

What are you trying to test in your switch statement? 你想在switch语句中测试什么? Surely you must know SOMETHING about the type of object that is coming in. 当然你必须知道有关正在进入的物体类型的东西。

Consider: how would you structure a switch statement when you could accept either a Product or a Customer type in your method? 考虑一下:当您可以在方法中接受Product或Customer类型时,如何构造switch语句? What is the logical choice that you want the compiler to make for you? 您希望编译器为您做出的逻辑选择是什么? If you want the compiler to choose an action based on the price of a product, that doesn't work for Customer objects. 如果希望编译器根据产品价格选择操作,则不适用于Customer对象。 However, if both Products and Customers have a CreateDate field that you want to pivot on, you could extract that into an interface and use that as a generic constraint on the method. 但是,如果产品和客户都有您想要转向的CreateDate字段,则可以将其提取到接口中,并将其用作方法的通用约束。

Add an appropriate constraint to your generic method signature that encapsulates what you do know about the types that you expect, and then you will be able to switch: 为您的通用方法签名添加适当的约束,该签名封装了您对所期望的类型的了解,然后您将能够切换:

public interface ICreateDate {

   public DateTime CreateDate { get; set; }

}

 public T testfunction<T, U>(U numb) where U : ICreateDate
    {
        switch(numb.CreateDate.DayOfWeek){

            case DayOfWeek.Monday:

        }
        ....
    }

This does not work because the case sections inside of the switch need to be compile-time constants of a specific type. 这不起作用,因为switch内部的case部分需要是特定类型的编译时常量。 For example, you couldn't do case 1: because numb could be a string; 例如,你不能做case 1:因为numb可能是一个字符串; neither can you do case "foo": because numb could be an integer. 你也不能做case "foo":因为numb可能是一个整数。 The type of numb must be known at compile-time in order to use it as the switch variable, because the compiler needs to know what kinds of constant values are valid in the case sections. 必须在编译时知道numb的类型才能将其用作切换变量,因为编译器需要知道在case部分中哪种常量值有效。

The logic behind this is that the switch statement doesnt work with every type and so it wont let you switch on a generic type. 这背后的逻辑是switch语句不适用于所有类型,因此它不会让你打开泛型类型。

Actually it works with a pretty small number of types, int, char, string, bool (not sure on this one) and maybe a couple of more primitive types. 实际上它适用于很少的类型,int,char,string,bool(在这一个上不确定),也许还有一些更原始的类型。

Primitive types are the types that come built in to the language, basically everything this isnt a class/struct/union etc... 原始类型是内置于语言中的类型,基本上所有这些都不是类/结构/联合等...

As many others have said, switch statements must be compile time constants as explained in the C# language specification (found here: http://www.microsoft.com/en-us/download/details.aspx?id=7029 ) in section 8.7.2: 正如许多其他人所说,switch语句必须是编译时常量,如C#语言规范(在此处: http//www.microsoft.com/en-us/download/details.aspx?id = 7029 )中所述。 8.7.2:

A switch-statement consists of the keyword switch, followed by a parenthesized expression (called the switch expression), followed by a switch-block. switch语句由关键字switch,后跟括号表达式(称为switch表达式),后跟switch-block组成。 The switch-block consists of zero or more switch-sections, enclosed in braces. 开关组由零个或多个开关部分组成,用括号括起来。 Each switch-section consists of one or more switch-labels followed by a statement-list (§8.2.1). 每个switch-section包含一个或多个switch-labels,后跟一个statement-list(第8.2.1节)。 The governing type of a switch statement is established by the switch expression. switch语句的控制类型由switch表达式建立。

• If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or an enum-type, or if it is the nullable type corresponding to one of these types, then that is the governing type of the switch statement. •如果switch表达式的类型是sbyte,byte,short,ushort,int,uint,long,ulong,bool,char,string或enum-type,或者它是与这些类型之一对应的可空类型那么这就是switch语句的管理类型。

• Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable type corresponding to one of those types. •否则,从switch表达式的类型到以下可能的控制类型之一,必须存在一个用户定义的隐式转换(第6.4节):sbyte,byte,short,ushort,int,uint,long,ulong,char, string,或者,与这些类型之一对应的可空类型。

• Otherwise, if no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs. •否则,如果不存在此类隐式转换,或者存在多个此类隐式转换,则会发生编译时错误。

The constant expression of each case label must denote a value that is implicitly convertible (§6.1) to the governing type of the switch statement. 每个case标签的常量表达式必须表示一个可隐式转换(§6.1)的值到switch语句的控制类型。 A compile-time error occurs if two or more case labels in the same switch statement specify the same constant value. 如果同一switch语句中的两个或多个case标签指定相同的常量值,则会发生编译时错误。

With that in mind even if you could achieve this, it would be a code-smell at best. 考虑到这一点,即使你能实现这一点,它也会充满代码味道。 The primary reason for this is it would violate the Open/Closed principle ( http://www.oodesign.com/open-close-principle.html ) in that you would have to make modifications to this section of code as new types of U (whatever they are) are introduced. 这样做的主要原因是它违反了开放/封闭原则( http://www.oodesign.com/open-close-principle.html ),因为你必须修改这部分代码作为新的类型U(无论它们是什么)被引入。 Consider this BAD example: 考虑这个不好的例子:

    public enum ItemType : int {

        Default = 0,

        Basic = 1,

        Advanced = 2,

        Expert = 3
    }

    public class NotSoGoodItem {

        public string Name { get; set; }

        public int Id { get; set; }

        public ItemType DataType { get; set; }

        public List<String> BasicSettings {get; set;}

        public List<String> AdvancedSettings {get; set;}

        public List<String> ExperSettings {get; set;}

    }

    public static NotSoGoodItem CreateNewItem(ItemType item) {
        //item is inherently an int
        switch (item) {
            case ItemType.Default | ItemType.Basic:
                return new NotSoGoodItem() { Name = "Basic Item", BasicSettings = new List<String>() };
            case ItemType.Advanced:
                return new NotSoGoodItem() { Name = "Advanced Item", AdvancedSettings = new List<String>() };
            case ItemType.Expert:
                return new NotSoGoodItem() { Name = "Expert Item", AdvancedSettings = new List<String>() };
            default:
                return null;
        }
    }

In this case, the switch statement uses the enum value (which inherits from int and satisfies the specification requirement) to determine which property to fill and what values to set. 在这种情况下,switch语句使用枚举值(继承自int并满足规范要求)来确定要填充的属性以及要设置的值。 This is problematic as if you wanted to introduce a new level or ItemType of say, Guru, you would have to modify at least 3 code files to make this change (the enum, the NotSoGoodItem to add the property, and the method that does the creation). 这是有问题的,因为如果你想引入一个新的级别或ItemType ,比如Guru,你将不得不修改至少3个代码文件来进行这个改变(enum, NotSoGoodItem来添加属性,以及执行创建)。

Instead by using generics, this code can be simplified using the following Good example: 相反,通过使用泛型,可以使用以下良好示例简化此代码:

    public abstract class GoodItem {

        protected GoodItem() { }

        private string _name;

        public virtual string Name { get { return string.Concat(Prefix, _name); } set { _name = value; } }

        protected virtual string Prefix { get; set; }

        public virtual int Id { get; set; }

        public virtual List<String> Settings { get; set; }
    }

    public class BasicItem : GoodItem {

        public BasicItem()
            : base() {
                Prefix = "Basic";
        }
    }

    public class AdvancedItem : GoodItem {
        public AdvancedItem()
            : base() {
                Prefix = "Advanced";
        }
    }

    public class ExpertItem : GoodItem {
        public ExpertItem()
            : base() {
                Prefix = "Expert";
        }
    }
    public static T CreateNewItem<T>() where T : GoodItem {
        return Activator.CreateInstance<T>();
    }

With this approach, zero code files must be modified. 使用此方法,必须修改零代码文件。 All that needs to be done is to add a new type that inherits from GoodItem . 所有需要做的就是添加一个继承自GoodItem的新类型。

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

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