I would like to create a value type tuple dynamically from say a collection of values.
Example: I have a given IEnumerable<T>
and I would like to create a tuple based on that collection.
How can I achieve that?
It seems that the access within a value type tuple can be achieved dynamically but nothing indicates that the same can be done for the creation of a value type tuple.
On of my purposes would be to leverage the properties of the Equality and HashCode of such tuples like described in this article
The question still isn't clear, but I assume you want to convert your a
collection into a value tuple of the form (a[0], a[1], a[2], …)
. This isn't supported through any built-in functionality. Furthermore, you would quickly run into limits, as .NET Framework only defines up to ValueTuple<T1, …, T7>
– going beyond that would require you to construct unwieldy nested value tuples using ValueTuple<T1, …, T7, TRest>
(e.g. ValueTuple<T1, …, T7, ValueTuple<T1, …, T7, ValueTuple<T1, …>>>
).
If you're seeking to achieve collection equality comparisons, you should use an IEqualityComparer<ICollection<T>>
instead. Here is a sample implementation from SequenceEqualityComparer
:
public class SequenceEqualityComparer<TElement> : EqualityComparer<IEnumerable<TElement>>
{
private readonly IEqualityComparer<TElement> _elementEqualityComparer;
public SequenceEqualityComparer()
: this(null)
{ }
public SequenceEqualityComparer(IEqualityComparer<TElement> elementEqualityComparer)
{
_elementEqualityComparer = elementEqualityComparer ?? EqualityComparer<TElement>.Default;
}
public new static SequenceEqualityComparer<TElement> Default { get; } = new SequenceEqualityComparer<TElement>();
public override bool Equals(IEnumerable<TElement> x, IEnumerable<TElement> y)
{
if (object.ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
if (x is ICollection<TElement> xCollection &&
y is ICollection<TElement> yCollection &&
xCollection.Count != yCollection.Count)
return false;
return x.SequenceEqual(y, _elementEqualityComparer);
}
public override int GetHashCode(IEnumerable<TElement> sequence)
{
if (sequence == null)
return 0;
unchecked
{
const uint fnvPrime = 16777619;
uint hash = 2166136261;
foreach (uint item in sequence.Select(_elementEqualityComparer.GetHashCode))
hash = (hash ^ item) * fnvPrime;
return (int)hash;
}
}
}
Edit: For the fun of it, here's my implementation to your actual question, using reflection and recursion:
public static object CreateValueTuple<T>(ICollection<T> collection)
{
object[] items;
Type[] parameterTypes;
if (collection.Count <= 7)
{
items = collection.Cast<object>().ToArray();
parameterTypes = Enumerable.Repeat(typeof(T), collection.Count).ToArray();
}
else
{
var rest = CreateValueTuple(collection.Skip(7).ToArray());
items = collection.Take(7).Cast<object>().Append(rest).ToArray();
parameterTypes = Enumerable.Repeat(typeof(T), 7).Append(rest.GetType()).ToArray();
}
var createMethod = typeof(ValueTuple).GetMethods()
.Where(m => m.Name == "Create" && m.GetParameters().Length == items.Length)
.SingleOrDefault() ?? throw new NotSupportedException("ValueTuple.Create method not found.");
var createGenericMethod = createMethod.MakeGenericMethod(parameterTypes);
var valueTuple = createGenericMethod.Invoke(null, items);
return valueTuple;
}
Sample use:
var collection = new[] { 5, 6, 6, 2, 8, 4, 6, 2, 6, 8, 3, 6, 3, 7, 4, 1, 6 };
var valueTuple = CreateValueTuple(collection);
// result: (5, 6, 6, 2, 8, 4, 6, (2, 6, 8, 3, 6, 3, 7, (4, 1, 6)))
If you don't mind Item8
being boxed, you could do away with reflection:
public static object CreateValueTuple<T>(IList<T> list)
{
switch (list.Count)
{
case 0: return default(ValueTuple);
case 1: return (list[0]);
case 2: return (list[0], list[1]);
case 3: return (list[0], list[1], list[2]);
case 4: return (list[0], list[1], list[2], list[3]);
case 5: return (list[0], list[1], list[2], list[3], list[4]);
case 6: return (list[0], list[1], list[2], list[3], list[4], list[5]);
case 7: return (list[0], list[1], list[2], list[3], list[4], list[5], list[6]);
default: return (list[0], list[1], list[2], list[3], list[4], list[5], list[6], CreateValueTuple(list.Skip(7).ToList()));
}
}
The difference is that the reflection-based method generates a result of type:
ValueTuple<int,int,int,int,int,int,int,ValueTuple<ValueTuple<int,int,int,int,int,int,int,ValueTuple<ValueTuple<int,int,int>>>>>
…whilst the switch-based method generates:
ValueTuple<int,int,int,int,int,int,int,ValueTuple<object>>
In each case, there is a redundant single-component ValueTuple<T>
wrapping the nested value tuples. This is an unfortunate design flaw of the ValueTuple.Create<T1, …, T8>
method implementation in the .NET Framework, and occurs even using the value tuple syntax (e.g. (1, 2, 3, 4, 5, 6, 7, (8, 9))
).
public static ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
{
return new ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>(item1, item2, item3, item4, item5, item6, item7, ValueTuple.Create(item8));
}
As canton7 mentions, you can work around it by using the ValueTuple<T1, …, T7, TRest>()
constructor directly, as shown in their answer.