Roi Shabtai
Roi Shabtai

Reputation: 3085

Generic Type as a Generic Type

I would like to use a generic type within another generic type.

but getting the following exception:

The type 'MockManagers.ToPos' cannot be used as type parameter 'Res' in the generic type or method 'MockManagers.IMockManager<Req,Res>'. There is no implicit reference conversion from 'MockManagers.ToPos' to 'MockManagers.MockResponseObject<System.IComparable>'.

I tried few option to solve this without any luck. This reference suggest to pass the inner generic type to as a parameter to the higher level generic type. is this the only solution?

The code:
My Interface:

/// <summary>
/// Represents IMockManager for mock object creation object.
/// </summary>
/// <typeparam name="Req">The request object Type.</typeparam>
/// <typeparam name="Res">The response object type.</typeparam>
public interface IMockManager<Req, Res>
    where Req : MockRequestObject
    where Res : MockResponseObject<IComparable>
{...//interface definitions}

objects implementatation:

/// <summary>
/// Represents Mock response for a request object
/// </summary>
public class ToPos : MockResponseObject<byte[]>
{... //implementation}

public class MockBaseObject
{
    public int Id { get; set; }
}

 public class MockResponseObject<T> : MockBaseObject
{
    /// <summary>
    /// The response content.
    /// </summary>
    public T Content { get; set; }

    /// <summary>
    /// Res creation date.
    /// </summary>
    public DateTime CreationDate { get; set; }
}

Upvotes: 3

Views: 831

Answers (2)

Timwi
Timwi

Reputation: 66573

You could just declare ToPos as deriving from MockResponseObject<IComparable> instead of MockResponseObject<byte[]>, but then hide the Content property with a new one of type byte[]. However, for this to work you need something that wraps a byte[] and implements IComparable. For example:

public class ByteArrayComparable : IComparable
{
    public byte[] Data { get; private set; }
    public ByteArrayComparable(byte[] data) { Data = data; }

    // Implement IComparable.CompareTo() method here!
    public int CompareTo(object obj) { ... }
}

public class ToPos : MockResponseObject<IComparable>
{
    public new byte[] Content
    {
        get
        {
            if (base.Content == null)
                return null;
            return ((ByteArrayComparable) base.Content).Data;
        }
        set
        {
            base.Content = value == null ? null : new ByteArrayComparable(value);
        }
    }
}

Upvotes: 2

Roman Starkov
Roman Starkov

Reputation: 61402

The issue here is that you want generic variance on a class. C# only supports generic variance on interfaces.

Here's a simplified version of what you seem to want to work:

// Works fine: "string" is compatible with "IComparable"
IComparable a = new string('a', 5);

// Error: "List<string>" is not compatible with List<IComparable>"
List<IComparable> b = new List<string>(); 

This is only possible for interfaces, however, and then only if they satisfy certain variance constraints. One such interface is the IEnumerable<out T> interface:

// Works fine: IEnumerable is covariant
IEnumerable<IComparable> c = new List<string>();

// Similarly, IEnumerable<string> is compatible with IEnumerable<IComparable>:
IEnumerable<string> d = null;
IEnumerable<IComparable> e = d;

So how do you fix this? Here's one idea.

First, you can't use byte[] since it's not IComparable. Let's use string as an example, but you'll have to find something else suitable that implements this interface.

Second, make MockResponseObject an interface. Moreover, make it covariant in T:

public interface IMockResponseObject<out T>
{
    T Content { get; }
    DateTime CreationDate { get; set; }
}

In order for this to work, Content can't be settable through the interface.

Last, update the rest of the code to use this interface:

interface IMockManager<Req, Res>
    where Req : MockRequestObject
    where Res : IMockResponseObject<IComparable>
{
}

public class ToPos : MockBaseObject, IMockResponseObject<string>
{
    public string Content
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public DateTime CreationDate
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
}

Observe that ToPos still has a Content with a setter: you can set content as long as it isn't through the interface.

With all these changes, the following is now valid and compiles fine:

static IMockManager<MockRequestObject, ToPos> manager = null;

Upvotes: 3

Related Questions