如何动态创建 .NET C# ValueType 元组?

Eho*_*ret 3 .net c# dynamically-generated .net-core valuetuple

我想从一组值动态创建一个值类型元组。

示例:我有一个给定的IEnumerable<T>,我想根据该集合创建一个元组。

我怎样才能做到这一点?

似乎可以动态实现值类型元组内的访问,但没有任何迹象表明可以对值类型元组的创建进行相同的操作。

我的目的之一是利用本文中描述的此类元组的 Equality 和 HashCode 的属性

Dou*_*las 5

问题仍然不清楚,但我假设您想将您的a集合转换为形式为 的值元组(a[0], a[1], a[2], …)。这不受任何内置功能的支持。此外,您很快就会遇到限制,因为 .NET Framework 最多只能定义ValueTuple<T1, …, T7>- 超出范围将需要您使用ValueTuple<T1, …, T7, TRest>(例如ValueTuple<T1, …, T7, ValueTuple<T1, …, T7, ValueTuple<T1, …>>>)构造笨拙的嵌套值元组。

如果您正在寻求实现集合相等比较,则应改用 an IEqualityComparer<ICollection<T>>。下面是一个示例实现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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:为了它的乐趣,这是我对您的实际问题的实现,使用反射和递归:

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;
}
Run Code Online (Sandbox Code Playgroud)

样品用途:

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)))
Run Code Online (Sandbox Code Playgroud)

如果你不介意Item8被装箱,你可以取消反射:

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()));
    }
}
Run Code Online (Sandbox Code Playgroud)

不同之处在于基于反射的方法会生成以下类型的结果:

ValueTuple<int,int,int,int,int,int,int,ValueTuple<ValueTuple<int,int,int,int,int,int,int,ValueTuple<ValueTuple<int,int,int>>>>>
Run Code Online (Sandbox Code Playgroud)

…而基于开关的方法生成:

ValueTuple<int,int,int,int,int,int,int,ValueTuple<object>>
Run Code Online (Sandbox Code Playgroud)

在每种情况下,都有一个冗余的单组件ValueTuple<T>包装嵌套的值元组。这是ValueTuple.Create<T1, …, T8>.NET Framework 中方法实现的一个不幸的设计缺陷,即使使用值元组语法(例如(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));
}
Run Code Online (Sandbox Code Playgroud)

正如 canton7 所提到的,您可以通过ValueTuple<T1, …, T7, TRest>()直接使用构造函数来解决它,如他们的回答所示。