繁体   English   中英

C# - 切换泛型

[英]C# - switch in generics

我试图在泛型方法中的开关上比较泛型类型参数。 这对我的解决方案不起作用。 原因是:参数必须是特定类型(bool,char,string,integral,enum)。

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

        }
        ....
    }

但它背后的意义是什么? 如果参数是通用的并且我想进行比较,为什么它必须是类型定义的变量?

你想在switch语句中测试什么? 当然你必须知道有关正在进入的物体类型的东西。

考虑一下:当您可以在方法中接受Product或Customer类型时,如何构造switch语句? 您希望编译器为您做出的逻辑选择是什么? 如果希望编译器根据产品价格选择操作,则不适用于Customer对象。 但是,如果产品和客户都有您想要转向的CreateDate字段,则可以将其提取到接口中,并将其用作方法的通用约束。

为您的通用方法签名添加适当的约束,该签名封装了您对所期望的类型的了解,然后您将能够切换:

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:

        }
        ....
    }

这不起作用,因为switch内部的case部分需要是特定类型的编译时常量。 例如,你不能做case 1:因为numb可能是一个字符串; 你也不能做case "foo":因为numb可能是一个整数。 必须在编译时知道numb的类型才能将其用作切换变量,因为编译器需要知道在case部分中哪种常量值有效。

这背后的逻辑是switch语句不适用于所有类型,因此它不会让你打开泛型类型。

实际上它适用于很少的类型,int,char,string,bool(在这一个上不确定),也许还有一些更原始的类型。

原始类型是内置于语言中的类型,基本上所有这些都不是类/结构/联合等...

正如许多其他人所说,switch语句必须是编译时常量,如C#语言规范(在此处: http//www.microsoft.com/en-us/download/details.aspx?id = 7029 )中所述。 8.7.2:

switch语句由关键字switch,后跟括号表达式(称为switch表达式),后跟switch-block组成。 开关组由零个或多个开关部分组成,用括号括起来。 每个switch-section包含一个或多个switch-labels,后跟一个statement-list(第8.2.1节)。 switch语句的控制类型由switch表达式建立。

•如果switch表达式的类型是sbyte,byte,short,ushort,int,uint,long,ulong,bool,char,string或enum-type,或者它是与这些类型之一对应的可空类型那么这就是switch语句的管理类型。

•否则,从switch表达式的类型到以下可能的控制类型之一,必须存在一个用户定义的隐式转换(第6.4节):sbyte,byte,short,ushort,int,uint,long,ulong,char, string,或者,与这些类型之一对应的可空类型。

•否则,如果不存在此类隐式转换,或者存在多个此类隐式转换,则会发生编译时错误。

每个case标签的常量表达式必须表示一个可隐式转换(§6.1)的值到switch语句的控制类型。 如果同一switch语句中的两个或多个case标签指定相同的常量值,则会发生编译时错误。

考虑到这一点,即使你能实现这一点,它也会充满代码味道。 这样做的主要原因是它违反了开放/封闭原则( http://www.oodesign.com/open-close-principle.html ),因为你必须修改这部分代码作为新的类型U(无论它们是什么)被引入。 考虑这个不好的例子:

    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;
        }
    }

在这种情况下,switch语句使用枚举值(继承自int并满足规范要求)来确定要填充的属性以及要设置的值。 这是有问题的,因为如果你想引入一个新的级别或ItemType ,比如Guru,你将不得不修改至少3个代码文件来进行这个改变(enum, NotSoGoodItem来添加属性,以及执行创建)。

相反,通过使用泛型,可以使用以下良好示例简化此代码:

    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>();
    }

使用此方法,必须修改零代码文件。 所有需要做的就是添加一个继承自GoodItem的新类型。

暂无
暂无

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

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