Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a built-in generic interface with covariant type parameter returned by an indexer?

In this thread

How to get null instead of the KeyNotFoundException accessing Dictionary value by key?

in my own answer I used explicit interface implementation to change the basic dictionary indexer behaviour not to throw KeyNotFoundException if the key was not present in the dictionary (since it was convinient for me to obtain null in such a case right inline).

Here it is:

public interface INullValueDictionary<T, U>
    where U : class
{
    U this[T key] { get; }
}

public class NullValueDictionary<T, U> : Dictionary<T, U>, INullValueDictionary<T, U>
    where U : class
{
    U INullValueDictionary<T, U>.this[T key]
    {
        get
        {
            if (ContainsKey(key))
                return this[key];
            else
                return null;
        }
    }
}

Since in a real application I had a list of dictionaries, I needed a way to access the dictionaries from the collection as an interface. I used simple int indexer to acess each element of the list.

var list = new List<NullValueDictionary<string, string>>();
int index = 0;
//...
list[index]["somekey"] = "somevalue";

The easiest thing was to do something like this:

var idict = (INullValueDictionary<string, string>)list[index];
string value = idict["somekey"];

The question raised when I decided to try to use covariance feature to have a collection of interfaces to use instead. So I needed an interface with covariant type parameter for the cast to work. The 1st thing that came to my mind was IEnumerable<T>, so the code would look like this:

IEnumerable<INullValueDictionary<string, string>> ilist = list;
string value = ilist.ElementAt(index)["somekey"];

Not that nice at all, besides ElementAt instead of an indexer is way worse. The indexer for List<T> is defined in IList<T>, and T there is not covariant.

What was I to do? I decided to write my own:

public interface IIndexedEnumerable<out T>
{
    T this[int index] { get; }
}

public class ExtendedList<T> : List<T>, IIndexedEnumerable<T>
{

}

Well, few lines of code (I don't even need to write anything in ExtendedList<T>), and it works as I wanted:

var elist = new ExtendedList<NullValueDictionary<string, string>>();
IIndexedEnumerable<INullValueDictionary<string, string>> ielist = elist;
int index = 0;
//...
elist[index]["somekey"] = "somevalue";
string value = ielist[index]["somekey"];

Finally the question: can this covariant cast be somehow achieved without creating an extra collection?

like image 289
horgh Avatar asked Jan 04 '13 09:01

horgh


People also ask

How could you make the type parameter T covariant?

A covariant type parameter is marked with the out keyword ( Out keyword in Visual Basic). You can use a covariant type parameter as the return value of a method that belongs to an interface, or as the return type of a delegate.

How do you declare a generic interface as contravariant?

You can declare a generic type parameter contravariant by using the in keyword. The contravariant type can be used only as a type of method arguments and not as a return type of interface methods. The contravariant type can also be used for generic constraints.

Can you be declared as contravariant?

A type can be declared contravariant in a generic interface or delegate only if it defines the type of a method's parameters and not of a method's return type. In , ref , and out parameters must be invariant, meaning they are neither covariant nor contravariant.

Which of the following syntax is used to declare a generic interface?

The general syntax to declare a generic interface is as follows: interface interface-name<T> { void method-name(T t); // public abstract method. } In the above syntax, <T> is called a generic type parameter that specifies any data type used in the interface.


1 Answers

You can try use IReadOnlyList<T>, which is implemented by List<T>.

Note that I've added one instance of NullValueDictionary<string, string> to List, so that you won't get ArgumentOutOfRangeException at elist[index] line.

IReadOnlyList<NullValueDictionary<string, string>> elist = new List<NullValueDictionary<string, string>>
                                                                    { 
                                                                        new NullValueDictionary<string, string>() 
                                                                    };
IReadOnlyList<INullValueDictionary<string, string>> ielist = elist;

int index = 0;
//...
elist[index]["somekey"] = "somevalue";
string value = elist[index]["somekey"];

Edit: I've searched for covariant interfaces and collections with indexes prior to .NET 4.5, but found none. Still I think there are a little bit easier solution, than to create separate interface - just to cast one collection to another.

List<INullValueDictionary<string, string>> ielist = elist.Cast<INullValueDictionary<string, string>>().ToList();

Or use covariance gained from arrays

INullValueDictionary<string, string>[] ielist = elist.ToArray()

LINQ has some optimization that work on whole type compatibility, so you won't iterate over sequence if those types are compatible.

Cast implementation taken from MONO Linq

public static IEnumerable<TResult> Cast<TResult> (this IEnumerable source)
{
    var actual = source as IEnumerable<TResult>;
    if (actual != null)
        return actual;

    return CreateCastIterator<TResult> (source);
}

Note that I have changed INullValueDictionary<T, U> interface to contain set in the property so that ielist[index]["somekey"] = "somevalue"; will work.

public interface INullValueDictionary<T, U> where U : class
{
    U this[T key] { get; set; }
}

But again - if creating a new Interface and class is ok for you and you don't want to mess around with casts everywhere - I think it is a good solution, if you have considered at the constraints, it gives.

In search of covariance in mscorlib

This probably won't be interesting to you, but I've just wanted to find out what Types are covariant in mscorlib assembly. By running next script I received only 17 types are covariant, 9 of which are Funcs. I have omitted IsCovariant implementation, because this answer is too long even without it

typeof(int).Assembly.GetTypes()
                    .Where(type => type.IsGenericType)
                    .Where(type=>type.GetGenericArguments().Any(IsCovariant))
                    .Select(type => type.Name)
                    .Dump();

//Converter`2 
//IEnumerator`1 
//IEnumerable`1 
//IReadOnlyCollection`1 
//IReadOnlyList`1 
//IObservable`1 
//Indexer_Get_Delegate`1 
//GetEnumerator_Delegate`1 
like image 116
Ilya Ivanov Avatar answered Nov 05 '22 15:11

Ilya Ivanov