I was searching for a way to put a Tuple<List>
into a Tuple<IEnumerable>
, and I found that Tuple has no covariance.
I decide to make my own implementation of an interface which subclass a tuple with a support for Covariance:
/// <summary>
/// A wrapper around Tuple for gaining covariance
/// </summary>
public static class ITuple
{
private class _ITuple<T1> : Tuple<T1>, ITuple<T1> { public _ITuple(T1 item1) : base(item1) { } }
public static ITuple<T1> Create<T1>(T1 item1) { return new _ITuple<T1>(item1); }
private class _ITuple<T1, T2> : Tuple<T1, T2>, ITuple<T1, T2> { public _ITuple(T1 item1, T2 item2) : base(item1, item2) { } }
public static ITuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2) { return new _ITuple<T1, T2>(item1, item2); }
private class _ITuple<T1, T2, T3> : Tuple<T1, T2, T3>, ITuple<T1, T2, T3> { public _ITuple(T1 item1, T2 item2, T3 item3) : base(item1, item2, item3) { } }
public static ITuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3) { return new _ITuple<T1, T2, T3>(item1, item2, item3); }
}
public interface ITuple<out T1> { T1 Item1 { get; } }
public interface ITuple<out T1, out T2> { T1 Item1 { get; } T2 Item2 { get; } }
public interface ITuple<out T1, out T2, out T3> { T1 Item1 { get; } T2 Item2 { get; } T3 Item3 { get; } }
Some tests and usage:
[TestMethod]
public void TestCompile()
{
// no assertion, just test that the compilation work
ITuple<Exception> item = ITuple.Create<NullReferenceException>(null);
ITuple<IEnumerable<int>> item2 = ITuple.Create<List<int>>(null);
}
[TestMethod]
public void TestEqual()
{
Assert.AreEqual(ITuple.Create(1, Tuple.Create("a")), ITuple.Create(1, Tuple.Create("a")));
Assert.AreNotEqual(ITuple.Create(1, Tuple.Create("a")), ITuple.Create(1, Tuple.Create("b")));
}
I choose to hide the implementation of the concrete class _ITuple
inside the static class, so you have access only to the public and static builder ITuple.Create
.
Problems:
Subclass and equality support
Subclassing the tuple is easy and there is nothing to do, and this is my concern. Is there something am I missing? I see no reason why equalities and hashcode souldn't work as expected with classic Tuple, but maybe you will find a problem somewhere which I didn't think of.
Naming convention
- Like the static class
Tuple
contains all theCreate
static methods,ITuple
, which is not an interface, has the static builder. At what point is this weird to name a non interface with anI
prefix? - At first, I called the
ITuple
interfaceICovariantTuple
, but found it too long, and there is noITuple
in the framework. Is this evident thatITuple
is covariant?
- Like the static class
1 Answer 1
I think that non-interface types shouldn't be called
ISomething
. You could just call the static classTuple
and differentiate it fromSystem.Tuple
by using a different namespace. Or you could call the class something likeTupleEx
, though that's not very descriptive.But the best option is if you could figure out a good name for the type that differentiates it from
System.Tuple
. I agree thatCovariantTuple
is quite long, though I don't have any better ideas.I also don't like the name
_ITuple
, even if it's not publicly visible. I would call it something likeTupleImpl
. (Again, if you figure out a good name, you could use that here too.)Regarding equality: your
ITuple
s will show as equal to normalTuple
s with the same contents. This most likely is what you want, but you should be aware of it.I don't think there are any other problems with equality or hash codes.
Consider not writing everything on a single line. I think it will make the code clearer.
It might make sense to add some way to convert
System.Tuple
toITuple
and back.
-
1\$\begingroup\$ +1, points 1 and 5 possibly lead us to a
TupleFactory
with methods likeCreate()
andCreateCovariant()
? \$\endgroup\$MattDavey– MattDavey2013年11月18日 17:01:21 +00:00Commented Nov 18, 2013 at 17:01