Reputation: 750
Context: .NET 4.0, C#
I'm creating a set of interfaces and a set of clases that implement them to provide some service. The clients use the concrete clases but call methods that are declared using the interfaces as parameter types.
A simplified example is this one:
namespace TestGenerics
{
// Interface, of fields
interface IField
{
}
// Interface: Forms (contains fields)
interface IForm<T> where T : IField
{
}
// CONCRETE CLASES
class Field : IField
{
}
class Form <T> : IForm<T> where T : IField
{
}
// TEST PROGRAM
class Program
{
// THIS IS THE SIGNATURE OF THE METHOD I WANT TO CALL
// parameters are causing the error.
public static void TestMethod(IForm<IField> form)
{
int i = 1;
i = i * 5;
}
static void Main(string[] args)
{
Form<Field> b = new Form<Field>();
Program.TestMethod(b);
}
}
}
The code makes sense to me, but I get the compiler error:
Argument 1: cannot convert from '
TestGenerics.Form<TestGenerics.Field>
' to 'TestGenerics.IForm<TestGenerics.IField>
' TestGenerics
I'm not sure what I'm doing wrong, I've read lots of pages on the internet but none solved my problem.
Is there a solution that would not modify that much the architecture of what I'm trying to build:
Edit:I designed the interfaces in a way such that they should be independent of concrete clases that implement them. The concrete clases could be loaded from a dll, but most of the application Works with the interfaces. In some cases I need to use concrete clases, specially when using clases that need to be serialized.
Thanks in advance.
Alejandro
Upvotes: 8
Views: 1658
Reputation: 8095
Others have pointed out the reasoning behind the error message, but let's examine the design of your sample code for a moment. Perhaps you're using a generic where none is needed.
You've already said you're using methods declared in the IField interface, so there may be no need to make your IForm class generic - simply have it store references to IField, instead of the generic argument 'T' (which is already guaranteed to be an IField anyway).
For instance, use:
public interface IForm
{
IEnumerable<IField> Fields { get; set; }
}
instead of
public interface IForm<T> where T : IField
{
IEnumerable<T> Fields { get; set; }
}
Upvotes: 7
Reputation: 152501
The problem is that Form<Field>
implements IForm<Field>
but not IForm<IField>
. You cannot use an inherited class (or interface) as a generic parameter unless it is marked as covariant with the out
identifier. However, marking your interface as covariant will restrict the usage significantly (basically making in an "output-only" interface like IEnumerable
) so it may not work for you.
One way to get it to work is to make TestMethod
generic as well:
public static void TestMethod<T>(IForm<T> form) where T:IField
{
int i = 1;
i = i * 5;
}
Upvotes: 13
Reputation: 3724
You can use Covariance, like so:
interface IForm<out T> where T : IField
{
}
More about Covariance and Contravariance here.
Upvotes: 10