Generic Collections and inheritance (again)

A

Adam Clauss

I ran into a problem a while back when attempting to convert existing .NET
1.1 based code to .NET 2.0 using Generic collections rather than Hashtable,
ArrayList, etc.

I ran into an issue because the old code allowed me to do what basically was
the following assignment:
class SomeClass
{
private Queue q;
SomeClass(Queue q)
{
this.q = q;
}
}

class SomeOtherClass : SomeClass
{
SomeOtherClass(Queue q) : base(q)
{
}
}


Where, when converted to generics is actually:
class SomeClass
{
private Queue<A> q;
SomeClass(Queue<A> q)
{
this.q = q;
}
}

class SomeOtherClass : SomeClass
{
SomeOtherClass(Queue<SubClassofA> q) : base(q)
{
}
}

Unfortunately Queeu<SubClassofA> cannot be passed to a Queue<A>.

Now, I was able to resolve the problem in this case by turning the class
which took this into a generic class itself:
class SomeClass<T> where T : A
and
class SomeOtherClass : SomeClass<SubClassofA>


However, I've now run into essentially the same situation with another
class. I do not believe I can use the same thing here though. The list
(Dictionary, actually) being passed into the class constructor could contain
various subclasses - not just one. Using the above solution of
class SomeOtherClass : SomeClass<SubClassofA>
limits me to ONLY using SubClassofA objects. But in this case there are
SEVERAL subclasses of A. I can guarrantee that the Dictionary here is ONLY
used in a "read" sense (the Dictionary is not altered). In fact, the only
time the list is used is to retrieve an element (which it currently expects
to be of type A). Various operations are then performed on that object, but
the list itself is never changed.

Does that "read-only" aspect buy me any alternatives?
As I see it now, my only option is to go back to the original Hashtable
object rather than Dictionary.
 
A

Adam Clauss

Yes - the fact that the original source is a Dictionary of SubclassOfA -
that Dictionary cannot simply be "passed" into a Dictionary of A. (Keys in
both cases are strings).
 
H

Helge Jensen

Adam said:
Unfortunately Queeu<SubClassofA> cannot be passed to a Queue<A>.

Well it's not really unfortunate -- it preseves static type-safety,
consider the following:

Queue<SubA> subaq = new Queue<SubA>();
Now, I was able to resolve the problem in this case by turning the class
which took this into a generic class itself:
class SomeClass<T> where T : A
and
class SomeOtherClass : SomeClass<SubClassofA>

I use adapters for ICollection, assuming Bar is a subtype of Foo:

ICollection<Bar> bars = ...;
However, I've now run into essentially the same situation with another
class. I do not believe I can use the same thing here though. The list
(Dictionary, actually) being passed into the class constructor could contain
various subclasses - not just one. Using the above solution of
class SomeOtherClass : SomeClass<SubClassofA>
limits me to ONLY using SubClassofA objects. But in this case there are
SEVERAL subclasses of A. I can guarrantee that the Dictionary here is ONLY
used in a "read" sense (the Dictionary is not altered). In fact, the only
time the list is used is to retrieve an element (which it currently expects
to be of type A). Various operations are then performed on that object, but
the list itself is never changed.

Does that "read-only" aspect buy me any alternatives?

Well, actually yes, it makes sure no runtime casts are required.
As I see it now, my only option is to go back to the original Hashtable
object rather than Dictionary.

Below are some adapters that shows what I mean and get you started on
writing your own adapters.

#region Immutable Exceptions
public class Immutable: InvalidOperationException {
public readonly object Instance;
protected Immutable(object instance): this(instance,
string.Format("{0} is immutable", instance)) { }
protected Immutable(object instance, string msg): base(msg) {
this.Instance = instance; }
public static Immutable Make(object instance) { return new
Immutable(instance); }
public static Immutable<T> Make<T>(T instance) { return new
Immutable<T>(instance); }
public static Immutable Make(object instance, string msg) { return
new Immutable(instance, msg); }
public static Immutable<T> Make<T>(T instance, string msg) { return
new Immutable<T>(instance, msg); }
}
public class Immutable<T>: Immutable {
public readonly new T Instance;
public Immutable(T instance, string msg) : base(instance, msg) {
this.Instance = instance; }
public Immutable(T instance) : this(instance, string.Format("{0} is
immutable", instance)) { }
}
#endregion

#region ICollection transformation
public class TransformedCollection<T, R> : ICollection<R>
{
public delegate R Transformer(T t);
public readonly ICollection<T> Parent;
public readonly Transformer Transform;
public readonly TransformedCollection<R, T>.Transformer Inverse;
protected T NoInverse(R r) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
Inverse({2})", this, typeof(T), typeof(R))); }
protected R NoTransform(T t) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
Transform({2})", this, typeof(R), typeof(T))); }
public TransformedCollection(ICollection<T> parent, Transformer
transform, TransformedCollection<R, T>.Transformer inverse)
{
this.Parent = parent;
this.Transform = transform == null ? NoTransform : transform;
this.Inverse = inverse == null ? NoInverse : inverse;
}
public TransformedCollection(ICollection<T> parent, Transformer
transform): this(parent, transform, null) {}
public TransformedCollection(ICollection<T> parent,
TransformedCollection<R,T>.Transformer inverse) : this(parent, null,
inverse) { }
public void Add(R r) { Parent.Add(Inverse(r)); }
public void Clear() { Parent.Clear(); }
public bool Contains(R r) { return Parent.Contains(Inverse(r)); }
public void CopyTo(R[] rs, int index) { foreach (T t in Parent)
rs[index++] = Transform(t); }
public bool Remove(R r) { return Parent.Remove(Inverse(r)); }
public int Count { get { return Parent.Count; } }
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public IEnumerator<R> GetEnumerator() { foreach (T t in Parent)
yield return Transform(t); }
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public class UpcastedCollection<T, R> : ICollection<R> where T: R
{
public readonly ICollection<T> Parent;
public UpcastedCollection(ICollection<T> parent) { this.Parent =
parent; }
public void Add(R r) { Parent.Add((T)r); }
public void Clear() { Parent.Clear(); }
public bool Contains(R r) { return r is T &&
Parent.Contains((T)r); }
public void CopyTo(R[] rs, int index) { foreach (T t in Parent)
rs[index++] = (R)t; }
public bool Remove(R r) { return r is T && Parent.Remove((T)r); }
public int Count { get { return Parent.Count; } }
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public IEnumerator<R> GetEnumerator() { foreach (T t in Parent)
yield return (R)t; }
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public class AddItem<T> : ICollection<T>
{
public readonly ICollection<T> Parent;
public readonly T Item;
public readonly IEqualityComparer<T> Comparer;
public AddItem(ICollection<T> parent, T item, IEqualityComparer<T>
comparer)
{
this.Parent = parent;
this.Item = item;
this.Comparer = comparer == null ? EqualityComparer<T>.Default :
comparer;
}
public void Add(T t) { Parent.Add(t); }
public bool Remove(T t)
{
if (!Parent.Remove(t))
throw Immutable.Make(this, string.Format("Cannot remove item
{0}", t));
else
return false;
}
public void Clear() { throw Immutable.Make(this,
string.Format("Cannot clear {0}", this)); }
public bool Contains(T t) { return Comparer.Equals(t, Item) ||
Parent.Contains(t); }
public int Count { get { return Parent.Count + 1; } }
public void CopyTo(T[] array, int index)
{
array[index++] = Item;
Parent.CopyTo(array, index);
}
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public IEnumerator<T> GetEnumerator()
{
yield return Item;
foreach (T t in Parent)
yield return t;
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
#endregion

#region IDictionary transformation
public class TransformedDictionary<K1, V1, K2, V2> : IDictionary<K2, V2>
{
public readonly IDictionary<K1, V1> Parent;
public readonly TransformedCollection<K1, K2>.Transformer KeyTransform;
public readonly TransformedCollection<K2, K1>.Transformer KeyInverse;
public TransformedCollection<V1, V2>.Transformer ValueTransform;
public TransformedCollection<V2, V1>.Transformer ValueInverse;
public K2 DefaultKeyTransform(K1 key) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
KeyTransform({2})", this, typeof(K2), typeof(K1))); }
public K1 DefaultKeyInverse(K2 key) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
KeyInversem({2})", this, typeof(K1), typeof(K2))); }
public V2 DefaultValueTransform(V1 value) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
ValueTransform({2})", this, typeof(K2), typeof(K1))); }
public V1 DefaultValueInverse(V2 value) { throw new
NotSupportedException(string.Format("{0} does not implement {1}
ValueInverse({2})", this, typeof(K1), typeof(K2))); }
public TransformedDictionary(
IDictionary<K1, V1> parent,
TransformedCollection<K1, K2>.Transformer keyTransform,
TransformedCollection<K2, K1>.Transformer keyInverse,
TransformedCollection<V1, V2>.Transformer valueTransform,
TransformedCollection<V2, V1>.Transformer valueInverse) {
this.Parent = parent;
this.KeyTransform = keyTransform == null ? DefaultKeyTransform :
keyTransform;
this.KeyInverse = keyInverse == null ? DefaultKeyInverse :
keyInverse;
this.ValueTransform = valueTransform == null ?
DefaultValueTransform : valueTransform;
this.ValueInverse = valueInverse == null ? DefaultValueInverse :
valueInverse;
}
public bool ContainsKey(K2 key) { return
Parent.ContainsKey(KeyInverse(key)); }
public void Add(K2 key, V2 value) { Parent.Add(KeyInverse(key),
ValueInverse(value)); }
public bool Remove(K2 key) { return Parent.Remove(KeyInverse(key)); }
public bool TryGetValue(K2 key, out V2 v2)
{
V1 v1;
bool found = Parent.TryGetValue(KeyInverse(key), out v1);
if (found)
v2 = ValueTransform(v1);
else
v2 = default(V2);
return found;
}
public V2 this[K2 key]
{
get { return ValueTransform(Parent[KeyInverse(key)]); }
set { Parent[KeyInverse(key)] = ValueInverse(value); }
}
public ICollection<K2> Keys { get { return new
TransformedCollection<K1, K2>(Parent.Keys, KeyTransform, KeyInverse); } }
public ICollection<V2> Values { get { return new
TransformedCollection<V1, V2>(Parent.Values, ValueTransform,
ValueInverse); } }
public void Clear() { Parent.Clear(); }
public void Add(KeyValuePair<K2, V2> kvp) { Parent.Add(new
KeyValuePair<K1, V1>(KeyInverse(kvp.Key), ValueInverse(kvp.Value))); }
public bool Contains(KeyValuePair<K2, V2> kvp) { return
Parent.Contains(new KeyValuePair<K1,V1>(KeyInverse(kvp.Key),
ValueInverse(kvp.Value))); }
public void CopyTo(KeyValuePair<K2, V2>[] kvps, int index)
{
foreach (KeyValuePair<K2, V2> kvp in this)
kvps[index++] = kvp;
}
public bool Remove(KeyValuePair<K2, V2> kvp) { return
Parent.Remove(new KeyValuePair<K1,V1>(KeyInverse(kvp.Key),
ValueInverse(kvp.Value))); }
public int Count { get { return Parent.Count; } }
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public IEnumerator<KeyValuePair<K2, V2>> GetEnumerator()
{
foreach (KeyValuePair<K1, V1> kvp in Parent)
yield return new KeyValuePair<K2, V2>(KeyTransform(kvp.Key),
ValueTransform(kvp.Value));
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public class FunctionDictionary<K, V> : IDictionary<K, V>
{
public readonly ICollection<K> Keys;
public readonly TransformedCollection<K, V>.Transformer Function;
public readonly IComparer<V> ValueComparer;
public FunctionDictionary(
ICollection<K> keys,
TransformedCollection<K, V>.Transformer function,
IComparer<V> valueComparer)
{
this.Keys = keys;
this.Function = function;
this.ValueComparer = valueComparer == null ? Comparer<V>.Default
: valueComparer;
}
public bool ContainsKey(K key) { return Keys.Contains(key); }
public void Add(K key, V value) { throw Immutable.Make(this); }
public bool Remove(K key) { throw Immutable.Make(this); }
public bool TryGetValue(K key, out V v)
{
bool found = Keys.Contains(key);
if (found)
v = Function(key);
else
v = default(V);
return found;
}
ICollection<K> IDictionary<K, V>.Keys { get { return Keys; } }
public ICollection<V> Values { get { return new
TransformedCollection<K, V>(Keys, Function); } }
public V this[K key]
{
get
{
if (Keys.Contains(key))
return Function(key);
else
throw new KeyNotFoundException(string.Format("{0} not in
{1}", key, this));
}
set { throw Immutable.Make(this); }
}
public void Add(KeyValuePair<K, V> kvp) { throw Immutable.Make(this); }
public void Clear() { throw Immutable.Make(this); }
public bool Contains(KeyValuePair<K, V> kvp) { return
Keys.Contains(kvp.Key) && ValueComparer.Compare(Function(kvp.Key),
kvp.Value) == 0; }
public void CopyTo(KeyValuePair<K, V>[] kvps, int index)
{
foreach (K key in Keys)
kvps[index++] = new KeyValuePair<K, V>(key, Function(key));
}
public bool Remove(KeyValuePair<K, V> kvp) { throw
Immutable.Make(this); }
public int Count { get { return Keys.Count; } }
public bool IsReadOnly { get { return true; } }
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
foreach (K key in Keys)
yield return new KeyValuePair<K, V>(key, Function(key));
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public class AddKeyValuePair<K, V> : IDictionary<K, V>
{
public readonly IDictionary<K, V> Parent;
public readonly K Key;
public V Value;
public readonly IEqualityComparer<K> KeyComparer;
public readonly IEqualityComparer<V> ValueComparer;
public AddKeyValuePair(IDictionary<K, V> parent, K key, V value,
IEqualityComparer<K> keyComparer, IEqualityComparer<V> valueComparer)
{
if (parent.ContainsKey(key))
throw new ArgumentException("parent contains key");
this.Parent = parent;
this.Key = key;
this.Value = value;
this.KeyComparer = keyComparer == null ?
EqualityComparer<K>.Default : keyComparer;
this.ValueComparer = valueComparer == null ?
EqualityComparer<V>.Default : valueComparer;
}
public bool ContainsKey(K k) { return KeyComparer.Equals(k, Key) ||
Parent.ContainsKey(k); }
public void Add(K k, V v)
{
if (KeyComparer.Equals(k, Key))
throw new ArgumentException(string.Format("{0} already
contains {1}", this, k));
else
Parent.Add(k, v);
}
public bool Remove(K k)
{
if (KeyComparer.Equals(k, Key))
throw Immutable.Make(this, string.Format("Cannot remove key:
{0}", k));
else
return Parent.Remove(k);
}
public bool TryGetValue(K k, out V v)
{
if ( KeyComparer.Equals(k, Key) )
{
v = Value;
return true;
}
else
return Parent.TryGetValue(k, out v);
}
public V this[K k]
{
get
{
if ( KeyComparer.Equals(k, Key) )
return Value;
else
return Parent[k];
}
set {
if (KeyComparer.Equals(k, Key))
Value = value;
else
Parent[k] = value;
}
}
public ICollection<K> Keys { get { return new
AddItem<K>(Parent.Keys, Key, KeyComparer); } }
public ICollection<V> Values { get { return new
AddItem<V>(Parent.Values, Value, ValueComparer); } }
public void Clear() { throw Immutable.Make(this,
string.Format("Cannot clear {0}", this)); }
public void Add(KeyValuePair<K, V> kvp)
{
if (KeyComparer.Equals(kvp.Key, Key))
throw new ArgumentException(string.Format("{0} already
contains {1}", this, kvp.Key));
else
Parent.Add(kvp);
}
public bool Contains(KeyValuePair<K, V> kvp)
{
V v;
return TryGetValue(kvp.Key, out v) &&
ValueComparer.Equals(kvp.Value, v);
}
public void CopyTo(KeyValuePair<K, V>[] kvps, int index)
{
kvps[index++] = new KeyValuePair<K, V>(Key, Value);
Parent.CopyTo(kvps, index + 1);
}
public bool Remove(KeyValuePair<K, V> kvp)
{
if (KeyComparer.Equals(kvp.Key, Key))
throw Immutable.Make(this, string.Format("Cannot remove key:
{0}", kvp.Key));
else
return Parent.Remove(kvp);
}
public int Count { get { return Parent.Count + 1; } }
public bool IsReadOnly { get { return Parent.IsReadOnly; } }
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
yield return new KeyValuePair<K, V>(Key, Value);
foreach (KeyValuePair<K, V> kvp in Parent)
yield return kvp;
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
#endregion
 
J

Jon Skeet [C# MVP]

Does that "read-only" aspect buy me any alternatives?

Yes. You can write a generic wrapper class which implements
IDictionary<T> and delegates all the reading to an IDictionary<U> where
U : T (where this dictionary would be supplied at construction), and
throws an exception for all "writing" operations. You'll have to write
wrappers for iteration etc as well, unfortunately.

My guess is that someone else has already written such wrappers, but I
don't know of any unfortunately.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top