I've created the following generic method

protected int GetSpecifiedIndexByName<T>(T collection, string itemName)
    {
      int count = 0;
      foreach (MyElement claim in collection)
      {
        if (itemName.Equals(claim.Name))
        {
          return count;
        }
        count++;
      }
    }

Which is called by

int index = GetSpecifiedIndexByName<MyCollection>(claimColl, itemName);

However, when I compile this I get the error

Error 1 foreach statement cannot operate on variables of type 'T' because 'T' does not contain a public definition for 'GetEnumerator'

I cannot cast the generic collection to the known collection directly i.e.

MyCollection foo = (MyCollection)collection;

But have to take the intermediary step

Object foo = collection;
MyCollection foobar = (MyCollection)foo;

This is clearly a lack of understanding about generics (and my 1st attempt at it) but any answers relating to why I get the problems (and solution) would be much appreciated.

Thanks

Dhaval

Recommended Answers

All 4 Replies

Upload a sample project demonstrating this behavior. You can upload a project by clicking on "Go Advanced" and then "Manager Attachments". You need to implement IEnumerable to use foreach() .

I've created the following generic method

protected int GetSpecifiedIndexByName<T>(T collection, string itemName)
    {
      int count = 0;
      foreach (MyElement claim in collection)
      {
        if (itemName.Equals(claim.Name))
        {
          return count;
        }
        count++;
      }
    }

You have two problems with this method. One is that there is no proof that T : IEnumerable. You need to use a where clause to specify that. The other problem is that not all control paths lead to a return statement.

You shouldn't be using generics for this kind of behavior. Instead, you should just have the function take an object that implements the IEnumerable interface:

protected int GetSpecifiedIndexByName(IEnumerable collection, string itemName)
    {
      int count = 0;
      foreach (MyElement claim in collection)
      {
        if (itemName.Equals(claim.Name))
        {
          return count;
        }
        count++;
      }
      return -1;
    }

You could also use IEnumerable<MyElement> in the signature. This is a good idea because it more tightly defines the interface of the function.

protected int GetSpecifiedIndexByName(IEnumerable<MyElement> collection, string itemName)
    {
        ...
    }

So, you should not be trying to implement a generic function at all. You just want a function that takes a parameter of type IEnumerable<MyElement>.

I cannot cast the generic collection to the known collection directly i.e.

In general, you can't cast from one type to an unrelated type. For example, you can't cast something from Int32 to ArrayList. This is because there's no way that can be correct! Similarly, you can't cast a generic type, which could be anything, to the type MyCollection. You can only cast things to superclasses and subclasses of the type you're casting.

But have to take the intermediary step

Object foo = collection;
MyCollection foobar = (MyCollection)foo;

In this example, you've cast up to Object (which is allowed because Object is a superclass of everything) and then you've cast down to MyCollection (which is allowed because MyCollection is a subclass of Object). The fact that you wish to do such a thing means you're trying to use the type system the wrong way, and I've already described how.

By the way, here's another possible way to write the function you want. It uses the where clause that I mentioned at the beginning of my reply. This would be a spurious use of generics, though, since generics are only useful when you want to prove that two types are equal.

protected int GetSpecifiedIndexByName<T>(T collection, string itemName)
        where T : IEnumerable<MyElement>
    {
      int count = 0;
      foreach (MyElement claim in collection)
      {
        if (itemName.Equals(claim.Name))
        {
          return count;
        }
        count++;
      }
      return -1;
    }
commented: Thanks! Very instructive. +4
commented: nice++ +5
commented: good explanation regarding down casting! +1

IEnumerable, which supports a simple iteration over a collection of a specified type.

protected int GetSpecifiedIndexByName<T>(T collection, string itemName) {
  int count = 0;
  foreach (MyElement claim in collection as IEnumerable<MyElement>){
       if (itemName.Equals(claim.Name)){
              return count;
       }
   count++;
   }
  return count;
}

Thanks for the replies.

The suggestion I liked

foreach (MyElement claim in collection as IEnumerable<MyElement>){

returns a

System.NullReferenceException : Object reference not set to an instance of an object.

I assume in this case because this is because we don't know what the collection passed in is at runtime?

To put into context what I'm trying to do, I want to have one method which can handle two sets of collections which both extend IEnumerable and both of which collections have their own results elements i.e. in HelloWorldCollection

public MyElement this[int index]
		{
			get { return (MyElement)ElementRaw(index); }
		}

which returns a MyElement object and similarly for FooBarCollection, a FooElement can be returned. Rather than writing two separate methods (or in my case below, one method with two separate for loops dependent on collection type) is there a better way I can implement this? Or, in short, as said earlier, this isn't a good use of generics and shaft this idea completely? Note that HelloWorldCollection and FooBar Collection both extend MyFooCollection.

protected int GetSpecifiedIndexByName<T>(T collection, string itemName)
      where T : MyFooCollection
    { 
      int count = 0;
      if (collection is HelloWorldCollection)      {

        foreach (MyElement  claim in collection)
        {
          if (itemName.Equals(claim.Name))
          {
            return count;
          }
          count++;
        }
      }

      else if (collection is FooBarCollection)
      {
        foreach (FooElement commission in collection)
        {
          if (itemName.Equals(commission.Name))
          {
            return count;
          }
          count++;
        }
      }
      
      throw new ApplicationException("Type with name " + itemName + " not found");
    }
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.