3

Okay. So I am currently trying to create a list of an interface in C# that takes an interface as a parameter. To make this clearer, let me give an example:

public interface IPlate<T> where T : IWaffle {}
public interface IWaffles {}

public class BelgiumWaffle : IWaffle {}
public class FalafelWaffle : IWaffle {}
public class HugePlate : IPlate<BelgiumWaffle> {}
public class SmallPlate : IPlate<FalafelWaffle> {}


// Now, I want to do the following:
var plates = new List<IPlate<IWaffle>>();
plates.add(new HugePlate());
plates.add(new SmallPlate());

The goal is to be able to serialize a list of IPlate objects into XML. I was hoping to use generics to do this but I keep getting errors telling me that there are some argument errors when trying to add (aka - the types don't match up). Just not sure what I'm doing wrong here. It seems right to me but I must be missing something (obviously).

Update: I should mention that this is .NET v3.5

Update: Sorry! Some typos when writing the question regarding definition of Plate classes.

6 Answers6

4

I think you should use covariance (.NET 4.0)

public interface IPlate<out T> where T : IWaffle {}

and replace IPancake with IWaffle

var plates = new List<IPlate<IPancake>>();
Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • Sorry, mixing up examples from my code I was playing with (changed to `IWaffles`) –  Oct 25 '12 at 13:59
  • That's really cool. Do you happen to know how to achieve the same thing with v3.5? Unfortunately, that is the version we are stuck with right now. –  Oct 25 '12 at 14:04
  • 1
    Define a non-generic `IPlate` interface that will be a base for a generic one and use `List`. I don't think you can achieve more type safety in 3.5 – Jakub Konecki Oct 25 '12 at 14:44
2

Neither HugePlate nor SmallPlate implement IPlate<IPancake> interface which is required by plates list.

  • Sorry, mixup. Has been updated. (shouldn't this be a comment?) –  Oct 25 '12 at 14:00
  • @John I still see the `HugePlate` (which is incorrect due BelgiumWaffle is a class name and there should be a variable name), and no `IPlate` in that line (nor the next one). – Mariano Desanze Oct 25 '12 at 14:05
  • @Protron Why is `HugePlate` incorrect? It compiles and runs for my tests? Also seems like proper use to me given `HugePlate` inherits from `IPlate where T : IWaffle` and `BelgiumWaffle` is of `IWaffle` –  Oct 25 '12 at 14:13
  • @Protron and I should be using a variable name in my class declaration? Really?? –  Oct 25 '12 at 14:13
  • @John In your original declaration that part did not compile. The word `BelgiumWaffle` was in the generic part of the class name declaration. Now you move it to the generics part of the inheritance (which is ok). And by variable name I meant like the `T` in your IPlate declaration (if that is what your're asking). – Mariano Desanze Oct 25 '12 at 14:45
1

Besides covariance (as already pointed out by @JakubKonecki), your definitions for HugePlate and SmallPlate look incorrect, as they need to implement IPlate.

Try this:

public interface IPlate<out T> where T : IWaffle {}
public interface IWaffle {}

public class BelgiumWaffle : IWaffle {}
public class FalafelWaffle : IWaffle {}
public class HugePlate<T> : IPlate<T> where T : IWaffle {}
public class SmallPlate<T> : IPlate<T> where T : IWaffle {}
Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • Hey, you're right that I do have an error in the question (typo, Sorry!) and I have updated it. However, the class-definitions that I have (for my purposes) will need to go ahead and define T for it's implementation of IPlate. –  Oct 25 '12 at 14:16
  • So after that you just need to put `out` into definition of `IPlate` :) – Roman Pekar Oct 25 '12 at 14:18
  • @John I wasn't nitpicking, I understand this is a contrived example just to show your issue... It's just that I didn't know what parts you just mistyped in your example and which were actual issues in your real code :) – Paolo Falabella Oct 25 '12 at 14:20
1

In the .NET Framework 3.5 you don't have the out parameter to use on generics covariance like in .NET Framework 4.0.

You could try to workaround it with a non-generic version of your IPlate (in this case I name it IPlateNG).

Consider the following example in .NET Framework 4.0 (I had to expand it to show my point):

using System;
using System.Collections.Generic;

public interface IWaffle { string Eat(); }
// on C# 4.0 you just put the "out" to mark the covariance (and that is it)
public interface IPlate<out T> where T : IWaffle { T GetMyWaffle(); }

public class BelgiumWaffle : IWaffle {
    public string Eat() { return "Eating a Belgium Waffle"; }
    public string Breakfast() { return "Breakfasting a Belgium Waffle"; }
}
public class FalafelWaffle : IWaffle {
    public string Eat() { return "Eating a Falafel Waffle"; }
    public string Dinner() { return "Having dinner with a Falafel Waffle"; }
}
public class HugePlate : IPlate<BelgiumWaffle> {
    public BelgiumWaffle GetMyWaffle() { return new BelgiumWaffle(); }
}
public class SmallPlate : IPlate<FalafelWaffle> {
    public FalafelWaffle GetMyWaffle() { return new FalafelWaffle(); }
}

class Program
{
    static void Main(string[] args)
    {
        var plates = new List<IPlate<IWaffle>>();
        plates.Add(new HugePlate());
        plates.Add(new SmallPlate());

        IPlate<IWaffle> aPlate = plates[0];
        // Anyway, when you get a member of the collection you'll get the interface, not a concrete class (obviously).
        IWaffle aWaffle = aPlate.GetMyWaffle();
        // So you cannot invoke any specifics (like Breakfast or Dinner)
        Console.WriteLine(aWaffle.Eat());

        // But if you cast the member of the collection to the specific class (or interface)
        IPlate<FalafelWaffle> aSmallPlate = (SmallPlate)plates[1];
        // Then you'll get the concrete class without casting again
        FalafelWaffle aFalafel = aSmallPlate.GetMyWaffle();
        Console.WriteLine(aFalafel.Dinner());
    }
}

Now this would be the same for the .NET Framework 3.5:

using System;
using System.Collections.Generic;

public interface IWaffle { string Eat(); }
// In this case I define this extra inteface which is non-generic
// And inside it, we need a new method equivalent to the one on the generic one
public interface IPlateNG { IWaffle GetWaffle(); }
// And make the generic one implement the non-generic one
public interface IPlate<T> : IPlateNG where T : IWaffle { T GetMyWaffle(); }

public class BelgiumWaffle : IWaffle {
    public string Eat() { return "Eating a Belgium Waffle"; }
    public string Breakfast() { return "Breakfasting a Belgium Waffle"; }
}
public class FalafelWaffle : IWaffle {
    public string Eat() { return "Eating a Falafel Waffle"; }
    public string Dinner() { return "Having dinner with a Falafel Waffle"; }
}
public class HugePlate : IPlate<BelgiumWaffle> {
    // This extra method is needed due the lack of the 'out' on the definition
    public IWaffle GetWaffle() { return GetMyWaffle(); }
    public BelgiumWaffle GetMyWaffle() { return new BelgiumWaffle(); }
}
public class SmallPlate : IPlate<FalafelWaffle> {
    // This extra method is needed due the lack of the 'out' on the definition
    public IWaffle GetWaffle() { return GetMyWaffle(); }
    public FalafelWaffle GetMyWaffle() { return new FalafelWaffle(); }
}

class Program
{
    static void Main(string[] args)
    {
        // The list cannot work with the IPlate<IWaffle> anymore. So here comes IPlateNG to the rescue
        var plates = new List<IPlateNG>();
        plates.Add(new HugePlate());
        plates.Add(new SmallPlate());

        IPlateNG aPlate = plates[0];
        // And instead of calling to the GetMyWaffle method we can call to the GetWaffle in this case
        IWaffle aWaffle = aPlate.GetWaffle();
        Console.WriteLine(aWaffle.Eat());

        IPlate<FalafelWaffle> aSmallPlate = (SmallPlate)plates[1];
        FalafelWaffle aFalafel = aSmallPlate.GetMyWaffle();
        Console.WriteLine(aFalafel.Dinner());
    }
}

Notice that I had to make an extra non-generic versions of the GetMyWaffle (named GetWaffle) on both IPlate concrete classes to workaround this lack of the "out" keyword. But the rest is pretty similar.

Mariano Desanze
  • 7,847
  • 7
  • 46
  • 67
0

Works in 3.5, thanks to @JakubKonecki who pointed about covariance

    public interface IWaffle { }
    public interface IPlate<out T> where T : IWaffle { }
    public interface IPancake : IWaffle { }

    public class BelgiumWaffle : IWaffle {}
    public class FalafelWaffle : IWaffle {}
    public class HugePlate : IPlate<BelgiumWaffle> {}
    public class SmallPlate : IPlate<FalafelWaffle> { }

    var plates = new List<IPlate<IWaffle>>();
    plates.Add(new HugePlate());
    plates.Add(new SmallPlate());
Roman Pekar
  • 107,110
  • 28
  • 195
  • 197
  • I think that is working. In my real code though, I'm getting errors because I have defined a property in the interface that returns a List and it's groaning about T being a variant type... hmm... –  Oct 25 '12 at 14:21
  • Does not work in 3.5. The `out` keyword for covariance was added in the .NET Framework 4. – Mariano Desanze Oct 25 '12 at 14:40
0

Can you use an abstract class instead of a interface for T?

public abstract class Waffle { }
public interface IPlate<T> where T : Waffle
{
    T Food
    {
        get;
        set;
    }
}

public class BelgiumWaffle : Waffle { }
public class FalafelWaffle : Waffle { }
public class HugePlate<T> : IPlate<T> where T : Waffle
{
    public HugePlate(T food)
    {
        this.Food = food;
    }

    public T Food
    {
        get;
        set;
    }
}

public class SmallPlate<T> : IPlate<T> where T : Waffle
{
    public SmallPlate(T food)
    {
        this.Food = food;
    }

    public T Food
    {
        get;
        set;
    }
}

public class Test
{
    Test()
    {
        var platesOfWaffle = new List<IPlate<Waffle>>();
        platesOfWaffle.Add(new HugePlate<Waffle>(new BelgiumWaffle()));
        platesOfWaffle.Add(new SmallPlate<Waffle>(new FalafelWaffle()));
    }
}
michaelalm
  • 200
  • 10