Frage

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?

War es hilfreich?

Lösung

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 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top