views:

709

answers:

5

I'm trying to show someone a use for interfaces in a crazy situation they've created. They have several unrelated objects in lists, and need to perform an operation on two string properties in each object. I'm pointing out that if they define the properties as part of an interface, they can use the interface object as the type for a method parameter that acts on it; for example:

void PrintProperties(IEnumerable<ISpecialProperties> list)
{
    foreach (var item in list)
    {
        Console.WriteLine("{0} {1}", item.Prop1, item.Prop2);
    }
}

This seems like it's all good, but the lists that need to be worked on aren't (and shouldn't) be declared with the interface as the type parameter. However, it doesn't seem like you can cast to a different type parameter. For example, this fails and I can't understand why:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Test> myList = new List<Test>();
            for (int i = 0; i < 5; i++)
            {
                myList.Add(new Test());
            }

            PrintList((IEnumerable<IDoSomething>)myList);
        }

        static void PrintList(IEnumerable<IDoSomething> list)
        {
            foreach (IDoSomething item in list)
            {
                item.DoSomething();
            }
        }
    }

    interface IDoSomething
    {
        void DoSomething();
    }

    public class Test : IDoSomething
    {
        public void DoSomething()
        {
            Console.WriteLine("Test did it!");
        }
    }
}

I can use the Enumerable.Cast<T> member to do this, but I was looking for a method that might work in .NET 2.0 as well. It seems like this should be possible; what am I missing?

+4  A: 

The problem is with the method, not with how it's called.....

void PrintProperties<SP>(IEnumerable<SP> list) where SP: ISpecialProperties
{
    foreach (var item in list)
    {
        Console.WriteLine("{0} {1}", item.Prop1, item.Prop2);
    }
}
James Curran
Exactly what I was just writing.
Joel B Fant
I stumbled across it and was coming to post it as well. This is kind of odd but I can understand why it's this way; you have to specify that the type is castable.
OwenP
+5  A: 

The reason it fails is because generics don't exhibit variance in C# (yet).

As for the fix for IEnumerable<T>, however, try this:

public static IEnumerable<TBase> SafeConvert<TBase, TDerived>(IEnumerable<TDerived> source)
    where TDerived : TBase
{
    foreach (TDerived element in source)
    {
        yield return element; // Implicit conversion to TBase
    }
}

EDIT: The other existing answer is better than the above for this particular case, but I'll leave this here as a generally useful thing if you do actually need to "convert" the sequence.

Jon Skeet
Ah Mr. Skeet - wonderful example that just helped me immensely, thanks!
BenAlabaster
+1  A: 

You can just use a foreach on the lists you have. The foreach does a built in cast. So if you take the loop out of the function you can write something like

List<Test> myList = new List<Test>();
for (int i = 0; i < 5; i++)
{
   myList.Add(new Test());
}

foreach (IDoSomething item in myList)
{
   item.DoSomething();
}
tpower
A good thing to note (the cast in foreach), but doesn't address passing the Lists of various derived types to the same function in case the function must do other things.
Joel B Fant
A: 

This doesn't answer your question (or the point of the exercise I guess :), but I'd just use reflection in this case by attaching special attributes to the properties of interest.

Jeff Kotula
Yikes! That's slow and complicated; I'd rather just tell my friend to duplicate code to vary by type.
OwenP
+1  A: 

What you want is called "interface covariance" and is not supported by C# at the moment. You can read about it on Eric Lippert's blog.

Constantin