C # - switch in generics

I tried to compare a typed typed parameter over a switch in a generic method. This doesn't work for me with my solution. Reason: the parameter must be of a specific type (bool, char, string, integer, enum).

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

        }
        ....
    }

      

But what is the point of this? If a parameter is generic and I want to perform a comparison, why should it be a specific type variable?

+3


source to share


4 answers


What are you trying to check in your switch statement? Of course, you must know SOMETHING about the type of object that is included.

Consider: how would you structure a switch statement if you could accept a product or 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 product price, this does not work for Customer objects. However, if both products and customers have a CreateDate field that you want to include, you can extract that into an interface and use that as a general method constraint.



Add an appropriate constraint to your generic method signature that encapsulates what you know about the types you expect, and then you can 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:

        }
        ....
    }

      

+6


source


This does not work because the sections case

inside the switch must be compile-time constants of a certain type. For example, you could not do case 1:

because it numb

could be a string; you also cannot do case "foo":

, because it numb

can be an integer. The type numb

must be known at compile time in order to use it as a switch variable, because the compiler needs to know which constant values ​​are valid in sections case

.



+4


source


The logic behind this is that a switch statement doesn't work with every type, so it won't let you include a generic type.

It actually works on a fairly small number of types, int, char, string, bool (not sure about this), and maybe a few more primitive types.

Primitive types are types that are built into the language, basically all of this is not class / structure / union, etc.

0


source


Like many others, switch statements must be compile-time constants as described in the C # language specification (here: http://www.microsoft.com/en-us/download/details.aspx?id=7029 ) in section 8.7 .2:

A switch statement consists of a switch keyword followed by a parenthesized statement (called a switch statement) followed by a block switch. A switch block consists of zero or more switch sections, enclosed in curly braces. Each breakout box consists of one or more jumper labels followed by a list of operators (§8.2.1). the control type of the switch statement is set by the switch expression.

• If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or enum-type, or if it is a null-valued type corresponding to one of these types, then it is a control switch statement type.

• Otherwise, only one user-defined implicit conversion (§6.4) must exist from the type of the switch expression for one of the following possible control types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or type c a null value corresponding to one of these types.

• Otherwise, if such an implicit conversion does not exist, or if more than one such implicit conversion exists, a compile-time error occurs.

The constant expression of each case label must mean a value that is implicitly convertible (§6.1) to the control type switch. A compile-time error occurs if two or more label cases in the same switch statement indicate the same constant value.

With that in mind, even if you could achieve this, it would be a code smell at best. The main reason for this is a violation of the Open / Closed Principle ( http://www.oodesign.com/open-close-principle.html ) in that you will have to modify this section of code as new U types are introduced (regardless of that they are). 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) to determine which property to populate and which values ​​to set. This is problematic, as if you wanted to introduce a new level or ItemType

Guru for example, you would need to change at least 3 code files to make this change (the enumeration NotSoGoodItem

, to add the property, and the method that creates).

Instead, 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, the zero code files must be modified. All you need to do is add a new type that inherits from GoodItem

.

0


source







All Articles