Question

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?

Was it helpful?

Solution

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);
    }
}

OTHER TIPS

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.

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();
}

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top