generic class with constraint IEqualityComparer - c#

I am pretty new to Generic class in C#. I was trying to create one and ran into compiler error that I am not sure how to get around it.
Basically, I have a class G that implements ICollection
public class G<T> : ICollection<T> where T : IEqualityComparer
{
private ArrayList _members = new ArrayList();
public void Add(T item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(T item)
{
throw new NotImplementedException();
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public int Count
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
throw new NotImplementedException();
}
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _members)
{
yield return (T)item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
I wanted to be able to do comparison in G and Find item in G, so I have put a constraint that T has to be implementing IEqualityComparer. Then, I have an actual class called IntegerClass that implement IEqualityComparer as below. So far, so good, no compiler error.
public class IntegerClass : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
throw new NotImplementedException();
}
public int GetHashCode(int obj)
{
throw new NotImplementedException();
}
}
However, when I tried to create an instance of G above. I got a compiler error.
class Program
{
static void Main(string[] args)
{
G<IntegerClass> i = new G<IntegerClass>();
}
}
The error is:
The type 'TestGeneric.IntegerClass' cannot be used as type parameter 'T' in the generic type or method 'TestGeneric.G<T>'.
There is no implicit reference conversion from 'TestGeneric.IntegerClass' to 'System.Collections.IEqualityComparer'
Could someone pinpoint what I have overlooked? Why would I need conversion? All I did was replacing class T with IntegerClass that implements IEqualityComparer interface. What should I do otherwise? I am new to this generic stuff, but have found it quite useful. I am thinking it could be very useful if I understand it correctly. Please help.
Update:
Based on some suggestion, I saw what was wrong and I updated the code as follow:
public class IntegerClass : IEqualityComparer
{
public bool Equals(object x, object y)
{
throw new NotImplementedException();
}
public int GetHashCode(object obj)
{
throw new NotImplementedException();
}
}
public class G<T> : ICollection<T> where T : IEqualityComparer
{
private ArrayList _members = new ArrayList();
public void Add(T item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(T item)
{
throw new NotImplementedException();
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public int Count
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
throw new NotImplementedException();
}
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _members)
{
yield return (T)item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
I think it might work but I got the warning below:
'TestGeneric.IntegerClass.Equals(object, object)' hides inherited member 'object.Equals(object, object)'. Use the new keyword if hiding was intended.
I know object has an Equals methods but the warning does not make sense. Should it say use the new keyword if hiding was NOT intended?

Your constraint refers to the non-generic System.Collections.IEqualityComparer interface, which is not the same as IEqualityComparer<T>.
You could fix that error by specify the generic type in the constraint.
However, that's not what you want; an IEqualityComparer is a class that compares other things.
You want where T : IEquatable<T>.

change your IntegerClass like below.
public class IntegerClass : IEqualityComparer<IntegerClass>
{
public bool Equals(IntegerClass IC1, IntegerClass IC2)
{
throw new NotImplementedException();
}
public int GetHashCode(IntegerClass obj)
{
throw new NotImplementedException();
}
}

Or you could resort it to the fact that Objects has an equality function.
Hence, this would work
public interface IMyComparer
{
object Comparer { get; }
}
public class IntegerClass : IMyComparer, IEqualityComparer<int>
{
public object Comparer { get { return this; } }
public bool Equals(int x, int y)
{
throw new NotImplementedException();
}
public int GetHashCode(int obj)
{
throw new NotImplementedException();
}
}
public class G<T> : ICollection<T> where T : IMyComparer
{
Your implementations
}
Hope it helps.

Related

Difference between the classes inherited from ObservableCollection and IList

I have created the two different classes. One class inherited from IList and another one class inherited from ObservableCollection. When we create the instance for those classes, I got the below results.
Inherited from IList
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Cells = new CellCollection();
}
private CellCollection cells;
public CellCollection Cells
{
get { return cells; }
set { cells = value; }
}
}
public class CellCollection : IList<OrderInfo>
{
public CellCollection()
{
}
public OrderInfo this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public bool IsReadOnly => throw new NotImplementedException();
public int Count => throw new NotImplementedException();
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(OrderInfo item)
{
throw new NotImplementedException();
}
public void CopyTo(OrderInfo[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public IEnumerator<OrderInfo> GetEnumerator()
{
throw new NotImplementedException();
}
public int IndexOf(OrderInfo item)
{
throw new NotImplementedException();
}
public void Insert(int index, OrderInfo item)
{
throw new NotImplementedException();
}
public bool Remove(OrderInfo item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
internal void Add(OrderInfo orderInfo)
{
}
void ICollection<OrderInfo>.Add(OrderInfo item)
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
Instance maintained for IList.
Inherited from ObservableCollection
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Cells = new CellCollection();
}
private CellCollection cells;
public CellCollection Cells
{
get { return cells; }
set { cells = value; }
}
}
public class CellCollection : ObservableCollection<OrderInfo>
{
public CellCollection()
{
}
}
Instance not maintained for Observable collection, Count only maintained
Could you explain the difference for both?
The debugger makes use of the debugger attributes the type is annotated with.
If a type does not use these attributes, the ToString() method of that type is called to get a text representation.
The ObservableCollection uses one of these methods to display the Count. Your own List implementation does none of this, so the base ToString() method is called which only returns the type name.
So to get a similar result for your own type, implement ToString() or annotate your CellCollection class with a DebuggerDisplayAttribute.
When the cursor is over a variable, you can see the variable name and the instance's debug label.
By default, the debug label is the result of ToString. For your class CellList, the method ToString cam from the base class Object and return the class's name. This display : CellList.
The attribute DebuggerDisplay allows define the instance's debug label (not to string). The class CellCollection inherit from ObservableCollection<T> than inherit from Collection<T> and the class Collection is declared with the attribute DebuggerDisplay.
[DebuggerDisplay("Count = {Count}")]
public class Collection<T>: IList<T>, IList, IReadOnlyList<T>
It's the same with all collection in .NET, like List.
If you set this attribute on your class CellList, you will see the same debug label.

Compilation error with primitives types with implementations of generic methods of an interface

I want to write an interface which allows freedom in the implementation. Basically I don't know the return types neither param types of the methods. I just want derived classes to implement this contract with same method names and param numbers.
So I can do like this:
public interface IImageRecognitionEngine<TFoo0, TFoo1, TFoo2, TFoo3>
{
TFoo0 Learn(TFoo1 param);
TFoo2 Recognize(TFoo3 param);
}
public class FooImageRecognitionEngine : IImageRecognitionEngine<byte[], string, List<double>, string>
{
public byte[] Learn(string param)
{
throw new NotImplementedException();
}
public List<double> Recognize(string param)
{
throw new NotImplementedException();
}
}
But I would much more prefer generic methods instead of the whole interface. But I don't understand why I can do this:
public interface IImageRecognitionEngine2
{
TFoo0 Learn<TFoo0, TFoo1>(TFoo1 param);
TFoo2 Recognize<TFoo2, TFoo3>(TFoo3 param);
}
public class FooExampleClass
{
}
public class FooExampleClass2
{
}
public class Foo1ImageRecognitionEngine2 : IImageRecognitionEngine2
{
public FooExampleClass Learn<FooExampleClass, FooExampleClass2>(FooExampleClass2 param)
{
throw new NotImplementedException();
}
public FooExampleClass Recognize<FooExampleClass, FooExampleClass2>(FooExampleClass2 param)
{
throw new NotImplementedException();
}
}
But with primitive types the compiler gives me errors:
public class Foo2ImageRecognitionEngine2 : IImageRecognitionEngine2
{
public byte[] Learn<byte[], string>(string param)
{
throw new NotImplementedException();
}
public List<double> Recognize<List<double>, string>(string param)
{
throw new NotImplementedException();
}
}
I don't want to be able to chose what types to use when I instantiate an object of the class implementation. For example, I don't want to write an implementation like this:
public class Foo2ImageRecognitionEngine2 : IImageRecognitionEngine2
{
public TFoo0 Learn<TFoo0, TFoo1>(TFoo1 param)
{
throw new NotImplementedException();
}
public TFoo2 Recognize<TFoo2, TFoo3>(TFoo3 param)
{
throw new NotImplementedException();
}
}
And being able to do this:
var fooEngine = new Foo2ImageRecognitionEngine2();
fooEngine.Learn<string, int>(52);
And this doesn't work also:
public interface IImageRecognitionEngine3
{
object Learn(object param);
object Recognize(object param);
}
public class Foo1ImageRecognitionEngine3 : IImageRecognitionEngine3
{
public byte[] Learn(string param)
{
throw new NotImplementedException();
}
public List<double> Recognize(string param)
{
throw new NotImplementedException();
}
}
Thank you
public FooExampleClass Learn<FooExampleClass, FooExampleClass2>(FooExampleClass2 param)
{
throw new NotImplementedException();
}
does not do what you think - FooExampleClass and FooExampleClass2 are not types, but type parameters which can be instantiated to any class. This means a client could do:
var e = new Foo1ImageRecognitionEngine2();
e.Learn<string, object>(new object());
Your code only appears to work because it does nothing.
Generic methods like this have to work for any parameters the client chooses so there's no way to restrict the parameters to work for particular parameters. If you want to do that you need to move them to the interface definition which you have in your first example.

C# HashSet<T> read-only workaround

Here is this sample code:
static class Store
{
private static List<String> strList = new List<string>();
private static HashSet<String> strHashSet = new HashSet<string>();
public static List<String> NormalList
{
get { return strList; }
}
public static HashSet<String> NormalHashSet
{
get { return strHashSet; }
}
public static IReadOnlyList<String> ReadonlyList
{
get { return (IReadOnlyList<String>)strList; }
}
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return (IReadOnlyCollection<String>)strHashSet; }
}
public static IReadOnlyList<String> Real_ReadonlyList
{
get { return (IReadOnlyList<String>)strList.AsReadOnly(); }
}
public static IReadOnlyCollection<String> Real_ReadonlyHashSet
{
get
{
List<String> tmpList = new List<String>(strHashSet);
return (IReadOnlyList<String>)(tmpList).AsReadOnly();
}
}
}
And here is a test code:
// normal behaviour
// you can modify the list and the hashset
Store.NormalList.Add("some string 1");
Store.NormalHashSet.Add("some string 1");
// tricky behaviour
// you can still modify the list and the hashset
((List<String>)Store.ReadonlyList).Add("some string 2");
((HashSet<String>)Store.ReadonlyHashSet).Add("some string 2");
// expected read-only behaviour
// you can NOT modify
// throws InvalidCastException
((List<String>)Store.Real_ReadonlyList).Add("some string 3");
// throws InvalidCastException
((HashSet<String>)Store.Real_ReadonlyHashSet).Add("some string 3");
My questions are these:
Is there a better solution for the "Real_ReadonlyHashSet" property?
Will Microsoft some day implement the "AsReadOnly" method to the HashSet<T>?
Here is the entirety of the code of .AsReadOnly()
public ReadOnlyCollection<T> AsReadOnly() {
Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
return new ReadOnlyCollection<T>(this);
}
The first line is not even necessary if you are not using CodeContracts. However, ReadOnlyCollection<T> only supports IList<T> which HashSet<T> does not support.
What I would do is make your own ReadOnlySet<T> class that takes in a ISet<T> and only passes through the read operations like ReadOnlyCollection<T> does internally.
UPDATE:
Here is a fully fleshed out ReadOnlySet<T> I quickly wrote up along with a extension method that adds a .AsReadOnly() on to anything that implements ISet<T>
public static class SetExtensionMethods
{
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set)
{
return new ReadOnlySet<T>(set);
}
}
public class ReadOnlySet<T> : IReadOnlyCollection<T>, ISet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set)
{
_set = set;
}
public IEnumerator<T> GetEnumerator()
{
return _set.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _set).GetEnumerator();
}
void ICollection<T>.Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void UnionWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void IntersectWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void ExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void SymmetricExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public bool IsSubsetOf(IEnumerable<T> other)
{
return _set.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<T> other)
{
return _set.IsSupersetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<T> other)
{
return _set.IsProperSupersetOf(other);
}
public bool IsProperSubsetOf(IEnumerable<T> other)
{
return _set.IsProperSubsetOf(other);
}
public bool Overlaps(IEnumerable<T> other)
{
return _set.Overlaps(other);
}
public bool SetEquals(IEnumerable<T> other)
{
return _set.SetEquals(other);
}
public bool Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void Clear()
{
throw new NotSupportedException("Set is a read only set.");
}
public bool Contains(T item)
{
return _set.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_set.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public int Count
{
get { return _set.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
}
Starting from .NET 5, the HashSet<T> class now implements the IReadOnlySet<T> interface. There is no built-in ReadOnlySet<T> wrapper though, analogous to the existing ReadOnlyDictionary<TKey, TValue> class for dictionaries, but implementing one is trivial:
public class ReadOnlySet<T> : IReadOnlySet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set) { ArgumentNullException.ThrowIfNull(set); _set = set; }
public int Count => _set.Count;
public bool Contains(T item) => _set.Contains(item);
public bool IsProperSubsetOf(IEnumerable<T> other) => _set.IsProperSubsetOf(other);
public bool IsProperSupersetOf(IEnumerable<T> other) => _set.IsProperSupersetOf(other);
public bool IsSubsetOf(IEnumerable<T> other) => _set.IsSubsetOf(other);
public bool IsSupersetOf(IEnumerable<T> other) => _set.IsSupersetOf(other);
public bool Overlaps(IEnumerable<T> other) => _set.Overlaps(other);
public bool SetEquals(IEnumerable<T> other) => _set.SetEquals(other);
public IEnumerator<T> GetEnumerator() => _set.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
The upcoming .NET 7 will also feature a new AsReadOnly extension method for IDictionary<TKey,TValue>s, so let's make one for ISet<T>s too:
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set) => new ReadOnlySet<T>(set);
Usage example:
HashSet<Item> items = new();
ReadOnlySet<Item> readOnlyItems = items.AsReadOnly();
You could write your own implementation of an IReadOnlyCollection<T> that wraps an IEnumerable<T> and a count:
public sealed class ReadOnlyCollectionFromEnumerable<T>: IReadOnlyCollection<T>
{
readonly IEnumerable<T> _data;
public ReadOnlyCollectionFromEnumerable(IEnumerable<T> data, int count)
{
_data = data;
Count = count;
}
public IEnumerator<T> GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count { get; }
}
Then you declare your ReadonlyHashSet property like this:
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return new ReadOnlyCollectionFromEnumerable<string>(strHashSet, strHashSet.Count); }
}
I think that would solve the issue.
HashSet implements the IReadOnlyCollection interface starting
with the .NET Framework 4.6; in previous versions of the .NET
Framework, the HashSet class did not implement this interface.
Read in learn.microsoft.com
In .NET Framework 4.6 release, the HashSet implements IReadOnlyCollection interface along with the ISet interface. link...
It does seem to.
You could also do this but maybe take a performance hit:
var foo = (IReadOnlyCollection<string>) mySet.toList();

populate IDataReader without reflection using any (specified) object Collection

I struggling to get something done when it suppose to be very easy to implement
say I have a car object
public class tblCarObj
{
public String Model;
public Int32 Year;
}
within these lines there is an implementation of reflection that is based on the fact that the object members are unknown (is this the only reason?)
which is nice for a generic functionality, but I am sacrificing functionality for performance if needed. and I wonder why both Links (see below) uses this approach.
can I simply use MyCarReader<tblCarObj>
instead of
//for this instance.
private IEnumerator<T> enumerator = null;
//List of all public fields in <T>
private List<FieldInfo> fields = new List<FieldInfo>();
public MyGenericDataReader(IEnumerable<T> enumerator)
{
this.enumerator = enumerator.GetEnumerator();
//Find the enumerator of all public fields
foreach (FieldInfo fieldinfo in typeof(T).GetFields(
BindingFlags.Instance |
BindingFlags.Public))
{
fields.Add(fieldinfo);
}
}
is there a way to implement this method without the use of reflection ?
background
the task Is to simply upload a collection of objects into database table,
List<tblCarObj>
where:
DataBase Table is Ready
DataBase Table matches the DataObject members
both are known at compile time.
and directly use it on SQLBulckCopy instead of a more complex object such as DataTable
MyGenericDataReader.cs
GenericListDataReader.cs
You are asking the runtime and the compiler to do something that it cannot do without reflecting over the type.
The information that allows the CLR and the compiler to know what properties are on the type are in the type's metadata. Reflection is the mechanism that queries metadata and serves it up in a meaningful way so that we can use it to discover the shape and content of a type. Without it, a type is essentially a named black box.
You could do what you want, but you'd have to code it by hand. The reason the libraries out there exist is because reflection makes it possible for you to avoid that extra work.
needs further test to try
eliminate unnecessary mappings Update: tested ok
(gain extra performance when MyTestObject properties/fields are same as columns)
if making it generic will effect it only slightly the use it with T
using :
public string sqlCon ="data source=(local);Initial Catalog=XXX;Integrated Security=True";
public SqlCommand Cmd;
public SqlConnection Conn;
public SqlDataReader Drdr;
public Form1()
{
InitializeComponent();
this.Conn = new SqlConnection(this.sqlCon);
this.Cmd = new SqlCommand("select * from [tblTestBC]", this.Conn);
useBulkCopy();
}
void justread()
{
this.Cmd.Connection.Open();
this.Drdr = this.Cmd.ExecuteReader();
if (this.Drdr.HasRows)
while (this.Drdr.Read())
{
}
this.Cmd.Connection.Close();
}
void useBulkCopy()
{
var bulkCopy = new SqlBulkCopy(this.Cmd.Connection);
bulkCopy.DestinationTableName = "tblTestBC";
//bulkCopy.ColumnMappings.Add("age", "age");
//bulkCopy.ColumnMappings.Add("name", "name");
this.Cmd.Connection.Open();
try
{
using (var dataReader = new mySimpleDataReader())
{
bulkCopy.WriteToServer(dataReader);
}
this.Cmd.Connection.Close();
}
catch (Exception e)
{
}
}
GenericIdataReader
namespace GenericIdataReader
{
public class MyTestObject
{
public int age;
public string name;
}
public class mySimpleDataReader : IDataReader
{
private IEnumerator<MyTestObject> enumerator = null;
public List<MyTestObject> prpLst { get; set; }
List<MyTestObject> lst()
{
var rt = new List<MyTestObject>(5);
for (int i = 0; i < rt.Capacity; i++)
{
var tmp = new MyTestObject { age = i, name = "MyTestObject->"+i };
rt.Add(tmp);
}
return rt;
}
public mySimpleDataReader()
{
this.prpLst = this.lst();
this.enumerator = this.prpLst.GetEnumerator();
}
public void Close()
{
throw new NotImplementedException();
}
public int Depth
{
get { throw new NotImplementedException(); }
}
public DataTable GetSchemaTable()
{
throw new NotImplementedException();
}
public bool IsClosed
{
get { throw new NotImplementedException(); }
}
public bool NextResult()
{
throw new NotImplementedException();
}
public bool Read()
{
return enumerator.MoveNext();
}
public int RecordsAffected
{
get { throw new NotImplementedException(); }
}
public void Dispose()
{
this.enumerator.Dispose();
}
public int FieldCount
{
get { return 2; }// must be setted
}
public bool GetBoolean(int i)
{
throw new NotImplementedException();
}
public byte GetByte(int i)
{
throw new NotImplementedException();
}
public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
{
throw new NotImplementedException();
}
public char GetChar(int i)
{
throw new NotImplementedException();
}
public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
{
throw new NotImplementedException();
}
public IDataReader GetData(int i)
{
throw new NotImplementedException();
}
public string GetDataTypeName(int i)
{
throw new NotImplementedException();
}
public DateTime GetDateTime(int i)
{
throw new NotImplementedException();
}
public decimal GetDecimal(int i)
{
throw new NotImplementedException();
}
public double GetDouble(int i)
{
throw new NotImplementedException();
}
public Type GetFieldType(int i)
{
throw new NotImplementedException();
}
public float GetFloat(int i)
{
throw new NotImplementedException();
}
public Guid GetGuid(int i)
{
throw new NotImplementedException();
}
public short GetInt16(int i)
{
throw new NotImplementedException();
}
public int GetInt32(int i)
{
throw new NotImplementedException();
}
public long GetInt64(int i)
{
throw new NotImplementedException();
}
public string GetName(int i)
{
throw new NotImplementedException();
}
public int GetOrdinal(string name)
{
throw new NotImplementedException();
}
public string GetString(int i)
{
throw new NotImplementedException();
}
public object GetValue(int i) // this is where data is being pooled
{
if (i > 0) return enumerator.Current.name;
// so need to create an object that will hold numeric index or simply change
//this to return an indexed object instead of an enumerator according to parmeter i value
return enumerator.Current.age;
}
public int GetValues(object[] values)
{
throw new NotImplementedException();
}
public bool IsDBNull(int i)
{
throw new NotImplementedException();
}
public object this[string name]
{
get { throw new NotImplementedException(); }
}
public object this[int i]
{
get { throw new NotImplementedException(); }
}
}
}

In C# 4.0 why can't an out parameter in a method be covariant?

Given this magical interface:
public interface IHat<out TRabbit>
{
TRabbit Take();
}
And this class hierarchy:
public class Rabbit { }
public class WhiteRabbit : Rabbit { }
I can now compile this:
IHat<WhiteRabbit> hat1 = null;
IHat<Rabbit> hat2 = hat1;
Which is great. But what if I define the interface differently:
public interface IHat<out TRabbit>
{
bool Take(out TRabbit r);
}
I'm indicating that the hat might be empty, using a separate boolean return value (the previous version would perhaps have returned a null rabbit from an empty hat). But I'm still only outputting a rabbit, so not doing anything logically different to the previous version.
The C# 4.0 compiler in the CTP gives an error in the interface definition - it requires 'out' method parameters to be of an invariant type. Is there a hard-and-fast reason why this isn't allowed, or is it something that might be addressed in a future version?
Interesting. However, at the CLI level there is no such thing as "out" - only "ref"; there is an attribute that helps compilers (for definite assignment) that says "you don't need to pass it in".
Maybe this restriction is because the CLI doesn't have "out", only "ref".
Although it's a bit of a hassle, you can use a covariance wrapper:
public class CovariantListWrapper<TOut, TIn> : IList<TOut> where TIn : TOut
{
IList<TIn> list;
public CovariantListWrapper(IList<TIn> list)
{
this.list = list;
}
public int IndexOf(TOut item)
{
// (not covariant but permitted)
return item is TIn ? list.IndexOf((TIn)item) : -1;
}
public TOut this[int index]
{
get { return list[index]; }
set { throw new InvalidOperationException(); }
}
public bool Contains(TOut item)
{
// (not covariant but permitted)
return item is TIn && list.Contains((TIn)item);
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (TOut t in this)
array[arrayIndex++] = t;
}
public int Count { get { return list.Count; } }
public bool IsReadOnly { get { return true; } }
public IEnumerator<TOut> GetEnumerator()
{
foreach (TIn t in list)
yield return t;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Insert(int index, TOut item) { throw new InvalidOperationException(); }
public void RemoveAt(int index) { throw new InvalidOperationException(); }
public void Add(TOut item) { throw new InvalidOperationException(); }
public void Clear() { throw new InvalidOperationException(); }
public bool Remove(TOut item) { throw new InvalidOperationException(); }
}
This lets you keep the collection as it was originally typed and refer to it covariantly without creating a detached copy, so that updates to the original are seen in the covariant use. Example:
class CovarianceWrapperExample
{
class Person { }
class Employee : Person { }
void ProcessPeople(IList<Person> people) { /* ... */ }
void Foo()
{
List<Employee> employees = new List<Employee>();
// cannot do:
ProcessPeople(employees);
// can do:
ProcessPeople(new CovariantListWrapper<Person, Employee>(employees));
}
}

Categories

Resources