5

I discovered a strange behavior when using property indexer (C#).

Consider the following program:

public class Program
{
    public static void Main(string[] args)
    {
        CustomMessageList cml = new CustomMessageList
        {
            new CustomMessage(), // Type1 
            new CustomMessage(), // Type1
            new CustomMessage(), // Type1
            new CustomMessage(), // Type1
            new CustomMessage(), // Type1
            new CustomMessage()  // Type1
        };

        // Empty
        IEnumerable<CustomMessage> x1 = cml[MessageType.Type2];

        // Contains all elements (6)
        IEnumerable<CustomMessage> x2 = cml[0]; // MessageType.Type1 ????

        // Does not compile!
        IEnumerable<CustomMessage> x3 = cml[1]; // NOT MessageType.Type2 ????
    }
}

public class CustomMessageList : List<CustomMessage>
{
    public IEnumerable<CustomMessage> this[MessageType type]
    {
        get { return this.Where(msg => msg.MessageType == type); }
    }
}

public class CustomMessage
{
    public MessageType MessageType { get; set; }
}

public enum MessageType
{
    Type1,
    Type2,
    Type3
}

Why do I get all results back when using the default indexer (the x2 variable)?

It seems that the int parameter (0) is automatically converted to the enum type (Type1). This is not what I was expecting....

Thanks in advance for the explanations!

Bidou
  • 7,378
  • 9
  • 47
  • 70

2 Answers2

10

The C# specification states that 0 (and only zero) is implicitly convertible to any enum type:

6.1.3 An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type and to any nullable-type whose underlying type is an enum-type.

If you don't explicitly assign your enumerations with values, they are 0..n, so Type1 is 0 in your case. This behavior has nothing to do with property indexers.

Since zero is the only implicit conversion that exists, this will compile fine:

IEnumerable<CustomMessage> x2 = cml[0];

But this will not compile because there is no implicit conversion for 1:

IEnumerable<CustomMessage> x2 = cml[1];

In that case, you would need to explicitly convert the value:

IEnumerable<CustomMessage> x2 = cml[(MessageType)1]; //Compiles fine
vcsjones
  • 138,677
  • 31
  • 291
  • 286
-1

That is because MessageType enum is in fact integer and Type1 enumeration value is zero. Try this:

public enum MessageType
{
    Type1 = 1,
    Type2,
    Type3
}
Zoran Horvat
  • 10,924
  • 3
  • 31
  • 43
  • That won't change a thing in the OP's example. The literal `0` will still be convertable to that enum type. – Servy May 16 '13 at 14:16
  • It will still be convertable, but the x2 variable will at least have the expected result – Bidou May 16 '13 at 14:23
  • @Servy The question is not why zero gets converted to enum, but why x2 contains 6 elements. The answer is because zero gets converted to enum AND enum defines value zero. Since the first cause is built into C# it cannot be modified. But the other one can - simply make enum start from 1 and you're fine. I haven't run the code but I expect variable x2 to contain zero elements after this modification. – Zoran Horvat May 16 '13 at 19:08
  • @sysexpand `"The question is not why zero gets converted to enum"` Oh really, `"It seems that the int parameter (0) is automatically converted to the enum type (Type1). This is not what I was expecting...."` It seems that's *exactly* what his question is. – Servy May 16 '13 at 19:11
  • @Servy Please focus on the question. Value 0 was converted to enum type MessageType but to a very particular value Type1. This is true ONLY BECAUSE value Type1 had underlying integer value zero. When enum is declared to start from 1, then there is no way to convert zero to MessageType.* for anything standing instead of *. One more thing: list should be populated with objects new CustomMessage() { MessageType = MessageType.Type1 }. And another addition - code does compile, I've tried it, and returns 0 elements in x2 as I predicted. – Zoran Horvat May 16 '13 at 19:38
  • @sysexpand Yes, it does, my point is *that's not the OP's question*. He's wondering *why it compiles*. That it compiles at all is what surprises him. He's not saying that it's vitally important that supplying `0` return zero results. That's not the question. The question is why passing `0` compiles at all. Saying that it returns six results is merely his observation informing him that it's clearly converting `0` to `Type1`. His question is "why does it do that?" not "how can I stop that from happening?" – Servy May 16 '13 at 19:49
  • @Servy "Why do I get all results back when using the default indexer (the x2 variable)?" Guess what my answer explains: why he gets all results back when using the default indexer (the x2 variable). No rocket science. When you said "That won't change a thing in the OP's example" you didn't quite get it, did you? It actually will change a thing - x2 will now have length 0 instead of 6. – Zoran Horvat May 16 '13 at 19:59
  • @sysexpand So how do you answer "**Why** do I get results back?" Why does he get results back? You aren't asnswering that question. Next, where does he say, "How to I make sure this returns nothing?" or anything along those lines? He never says anywhere his goal is to make it return nothing, his goal is to understand why the code does what it does. I don't see why you're still debating this; based on the OP's response of not upvoting this and accepting the other answer it's clear what his intentions were. If this was what he really wanted to do he would have accepted your answer. – Servy May 16 '13 at 20:03
  • @Servy He's getting the results back because MessageType.Type1 equals zero. That's not what he expected, literally. – Zoran Horvat May 16 '13 at 20:05
  • @sysexpand But that's not the whole story. `MessageType.Type1` equals `1`, but passing in `1` doesn't compile, rather than compiling and searching for all instances of that enum. You don't explain that difference which, based on the code sample, is clearly the point of confusion. That he tried it, and clearly can tell that `MessageType.Type2` maps to `1` indicates that everything you've shown here is information he has already figured out. What you've omitted is the missing piece of information, which the other answer has. – Servy May 16 '13 at 20:07
  • Since english is not my mother tongue, the question was maybe not clear enough... I knew indeed how to solve this problem; what I was not aware of, is that the zero value was implicity converted to enum and thus, that the enum overload has "priority" over the int method. The C# specification point 6.1.3 was therefore the best answer for me. – Bidou May 16 '13 at 21:33