3

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?

eddie_cat
  • 2,527
  • 4
  • 25
  • 43
lufonius
  • 43
  • 1
  • 7
  • 3
    Just follow through to the next logical step... what do you expect your `case` statements to be? – spender Sep 09 '14 at 14:51
  • 1
    Sadly it's because there's no way of knowing what U is going to be and whether there's any way to compare it to your cases. One potential alternative is to put your cases in a dictionary, and have the values be actions to take. Then call dict[numb](). – Nick Udell Sep 09 '14 at 14:51
  • You could add conditions to the generics class, like it must support ICheckValue interface and use the result of a certain method to fill your switch. – Schwarzie2478 Sep 09 '14 at 14:54
  • One of the many benefits of generics are that they allow you to avoid the need for messy, type-based switch statements (and difficult to maintain code) like this by allowing you to operate on some basic "shared knowledge" about the incoming type. Can you provide a more detailed example of how you intend to use this? – xDaevax Sep 09 '14 at 14:54
  • Side note: rare to see such word as ["numb"](http://www.merriam-webster.com/dictionary/numb) (devoid of sensation especially as a result of cold or anesthesia ) as variable name... Feels like some other name may be more appropriate or maybe it should be just `bool` (also `isNumb` may be better). – Alexei Levenkov Sep 09 '14 at 15:06

4 Answers4

6

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:

        }
        ....
    }
Jeff Fritz
  • 9,821
  • 7
  • 42
  • 52
  • While correct at first, the example does not show any switching *over `numb`*. – O. R. Mapper Sep 09 '14 at 14:53
  • 3
    The point made in the first sentence of this answer is that you must switch over something you know about `numb` to do a switch with it. – Jeff Fritz Sep 09 '14 at 14:54
  • 3
    I might consider using something other than a `bool` as an example switch expression, as switching over a `bool` is a rather silly way to express `if`/`else`. ;) – cdhowie Sep 09 '14 at 14:55
  • 1
    @cdhowie - Noted... Changed sample appropriately – Jeff Fritz Sep 09 '14 at 14:57
  • 1
    Exactly. The example shows switching over *something retrieved from `numb`*, not switching over `numb` itself. You do not need a generic constraint to do that, it is entirely beside the point that you can call a member of `numb` and switch over its (type-known-at-compile-time!) result. – O. R. Mapper Sep 09 '14 at 14:58
4

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.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
0

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

tomer.z
  • 1,033
  • 1
  • 10
  • 25
0

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.

xDaevax
  • 2,012
  • 2
  • 25
  • 36