locked
Enumeration as Indexer: Is C# not the strongly-typed?

    Question

  • Hello All,

    I found the need to use enumeration as indexer to simplify coding and just realized, I created bugs all over.

    The situation is like this...

     

    using System;
    using System.Collections.ObjectModel;
    
    namespace Demo
    {
     class Program
     {
     public enum AFormatType
     {
      None = 0,
      ItemA = 1,
      ItemB = 2,
      ItemC = 3,
      ItemD = 4,
     }
    
     public class AFormat
     {
      AFormatType _type;
    
      public AFormat(AFormatType type) { _type = type; }
    
      public AFormatType FormatType
      {
      get { return _type; }
      }
     }
    
     public class AFormatCollection : Collection<AFormat>
     { 
      public AFormat this[AFormatType type]
      {
      get
      {
       for (int i = 0; i < this.Count; i++)
       {
       AFormat format = base[i];
       if (format.FormatType == type)
       {
        return format;
       }
       }
    
       return null;
      }
      }
     }
    
     static void Main(string[] args)
     {
      AFormatCollection collFormats = new AFormatCollection();
      collFormats.Add(new AFormat(AFormatType.ItemA));
      collFormats.Add(new AFormat(AFormatType.ItemB));
      collFormats.Add(new AFormat(AFormatType.ItemC));
      collFormats.Add(new AFormat(AFormatType.ItemD));
    
      // Why should this be null in a strongly-typed language?
      AFormat testFormat = collFormats[0];
    
     }
     }
    }
    
    

     

    I was surprised to find that the testFormat is null. Is the C# enum nothing better than the C++?

    Best regards,
    Paul.

    Friday, December 17, 2010 7:01 AM

Answers

  • >>Matthew Watsonyou'll get the same result!

    The meaning is different! Collection<AFormat> is a collection and already has int-parameter indexer. The enum-parameter indexer is added for convenience - call it overloaded indexers.

    The only good explanation to this is provided at the CodeProject forum...

    http://www.codeproject.com/Messages/3701934/Re-Enumeration-as-Indexer-Is-Csharp-not-the-strong.aspx#xx3701803xx

    Best regards,
    Paul.

    • Marked as answer by Paul Selormey Friday, December 17, 2010 10:56 AM
    Friday, December 17, 2010 10:54 AM
  • Yes, like I said I wish that C# didn't silently convert the 0 to an enum type. It's very confusing; the fact that all integer indices other than zero work as expected shows that. :)

    The side-effect is that you can't reasonably overload the array subscripting operator with an enum indexer type without this annoying problem, and as you point out, that is solely due to the unfortunate silent conversion of zero to an enum.

    There is actually a way that you can prevent this from happening. You can add your own indexer that accepts an int parameter, and return the value from the base class. In your code example, it would look like this:

    public class AFormatCollection: Collection<AFormat>
    {
      public AFormat this[AFormatType type]
      {
        get
        {
          for (int i = 0; i < this.Count; i++)
          {
            AFormat format = base[i];
            if (format.FormatType == type)
            {
              return format;
            }
          }
    
          return null;
        }
      }
    
      public new AFormat this[int index]
      {
        get
        {
          return base[index];
        }
      }
    }
    
    
    Once you add that, the collFormats[0] call will end up calling the base class's indexer.

    • Marked as answer by Paul Selormey Friday, December 17, 2010 9:38 PM
    Friday, December 17, 2010 1:34 PM

All replies

  • Hi Paul,

    you ask for collFormats[0]. 0 is AFormatType.None and that is not present in your collection :-)

     

    If you add the line 'collFormats.Add(new AFormat(AFormatType.None));' your code should work :) 

    Or test  AFormat testFormat = collFormats[1]; and then you will get a AFormatType.ItemA

    Hope this helps,

    Wouter :)
    • Proposed as answer by WouterdeKortMVP Friday, December 17, 2010 8:14 AM
    • Unproposed as answer by Paul Selormey Friday, December 17, 2010 10:46 AM
    Friday, December 17, 2010 8:14 AM
  • This has nothing to do with strong-typing of enums. It's just because you're asking for a non-existent entry in the collection.

    If you replace:

    AFormat testFormat = collFormats[0];

    with:

    AFormat testFormat = collFormats[AFormatType.None];

    you'll get the same result!

    Friday, December 17, 2010 10:13 AM
  • >>WouterdeKortyou ask for collFormats[0]

    True, but Collection<AFormat> is a collection, so the actual request is for the zero-indexed item.

    Best regards,
    Paul.

    Friday, December 17, 2010 10:46 AM
  • >>Matthew Watsonyou'll get the same result!

    The meaning is different! Collection<AFormat> is a collection and already has int-parameter indexer. The enum-parameter indexer is added for convenience - call it overloaded indexers.

    The only good explanation to this is provided at the CodeProject forum...

    http://www.codeproject.com/Messages/3701934/Re-Enumeration-as-Indexer-Is-Csharp-not-the-strong.aspx#xx3701803xx

    Best regards,
    Paul.

    • Marked as answer by Paul Selormey Friday, December 17, 2010 10:56 AM
    Friday, December 17, 2010 10:54 AM
  • But when I step trough the code I see that the indexer is called with an index value of the enumeration type...

    So it doesn't go to the base class index[int]...


    Friday, December 17, 2010 11:12 AM
  • >>WouterdeKort: But when I step trough the code I see that the indexer is called with an index...

    That is the problem, which only the specification seems to clarify in the answer provided at the Codeproject forum. With the exception of (0) as in this case, any integer will access the base indexer.

    Just hope others will learn from this and not repeat this mistake. I was just lucky an exception was fired during the testing. Now, since that style of coding is repeated in many part of my library, I am now trying to replace the enum with similar struct types.

    Best regards,
    Paul.

    Friday, December 17, 2010 11:24 AM
  • >>Matthew Watsonyou'll get the same result!

    The meaning is different! Collection<AFormat> is a collection and already has int-parameter indexer. The enum-parameter indexer is added for convenience - call it overloaded indexers.

    The only good explanation to this is provided at the CodeProject forum...

    http://www.codeproject.com/Messages/3701934/Re-Enumeration-as-Indexer-Is-Csharp-not-the-strong.aspx#xx3701803xx

    Best regards,
    Paul.


    No, you do get exactly the same result, and the meaning is just the same. In both cases, you call the indexer that takes an AFormatType parameter, and in both cases that parameter has the same value of AFormatType.None. The meaning of the parameter in both cases is AFormatType.None.

    Now if you use a non-zero, then you DO get a different call (a call to the base indexer from Collection<>). But for the specific example that uses zero, then the meaning AND the result is the same.

    The issue here is that 0 is silently converted to an enum type - which is something that I wish C# didn't do. :)

    Friday, December 17, 2010 11:39 AM
  • >>Matthew Watson : The issue here is that 0 is silently converted to an enum type - which is something that I wish C# didn't do. :)

    So, where are you now? You said, this has nothing to do with strongly typed language, but now claiming it is silently converting to another type! It will not "silently convert" any other except 0, try 1 in a reversed list.

    Again, the only valid explanation is; that "silent conversion" is acceptable per the specifications.

    Best regards,
    Paul.

    Friday, December 17, 2010 12:04 PM
  • Yes, like I said I wish that C# didn't silently convert the 0 to an enum type. It's very confusing; the fact that all integer indices other than zero work as expected shows that. :)

    The side-effect is that you can't reasonably overload the array subscripting operator with an enum indexer type without this annoying problem, and as you point out, that is solely due to the unfortunate silent conversion of zero to an enum.

    There is actually a way that you can prevent this from happening. You can add your own indexer that accepts an int parameter, and return the value from the base class. In your code example, it would look like this:

    public class AFormatCollection: Collection<AFormat>
    {
      public AFormat this[AFormatType type]
      {
        get
        {
          for (int i = 0; i < this.Count; i++)
          {
            AFormat format = base[i];
            if (format.FormatType == type)
            {
              return format;
            }
          }
    
          return null;
        }
      }
    
      public new AFormat this[int index]
      {
        get
        {
          return base[index];
        }
      }
    }
    
    
    Once you add that, the collFormats[0] call will end up calling the base class's indexer.

    • Marked as answer by Paul Selormey Friday, December 17, 2010 9:38 PM
    Friday, December 17, 2010 1:34 PM
  • >>Matthew WatsonOnce you add that, the collFormats[0] call will end up calling the base class's indexer.

    This is a useful workaround for this issue.

    Best regards,
    Paul.

    Friday, December 17, 2010 9:38 PM
  • To get the first item of a collection, you can also use the extension method First(), or cast to the base collection.
    Saturday, December 18, 2010 10:26 PM
  • Am 17.12.2010 12:24, schrieb Paul Selormey:

    *WouterdeKort*: But when I step trough the code I see that the indexer is called with an index...

    That is the problem, which only the specification seems to clarify in the answer provided at the Codeproject forum.
    With the exception of (0) as in this case, any integer will access the base indexer.

    A pretty strange thing is that the enum-indexer get accessed even
    if '0' is passed in and there is no corresponding enum value of '0'
    - if you remove the "None = 0;" entry for example.

    The enum-indexer is not accessed, if you have a enum value of -1
    and you pass -1, weird! & thanks for clarifying,

    Chris

    Sunday, December 19, 2010 5:54 PM
  • There exist an implicit conversion from the constant 0 to any enum. And not from any other constant. And neither from a non-constant with a value of 0.

    An enum variable can have any value in the range of its underlying type (int by default), even if it doesn't define a name for it.

    Monday, December 20, 2010 2:51 AM