简体   繁体   中英

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).

    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? 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? 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. 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.

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. For example, you couldn't do case 1: because numb could be a string; neither can you do case "foo": because numb could be an integer. 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.

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.

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.

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:

A switch-statement consists of the keyword switch, followed by a parenthesized expression (called the switch expression), followed by a 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). The governing type of a switch statement is established by the switch expression.

• 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.

• 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.

• 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. A compile-time error occurs if two or more case labels in the same switch statement specify the same constant value.

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. 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. 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).

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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