Определить, является ли объект ValueTuple

У меня есть прецедент, когда мне нужно проверить, является ли значение С# 7 ValueTuple, и если да, проведите через каждый из элементов. Я пробовал проверять с помощью obj is ValueTuple и obj is (object, object), но оба они возвращают false. Я обнаружил, что могу использовать obj.GetType().Name и проверить, начинается ли это с "ValueTuple", но это кажется мне хромым. Любые альтернативы будут приветствоваться.

У меня также есть проблема с получением каждого элемента. Я попытался получить Item1 с найденным здесь решением: Как проверить, существует ли свойство в динамическом анонимном типе в С#?, но ((dynamic)obj).GetType().GetProperty("Item1") возвращает значение null. Надеюсь, что я смогу сделать while, чтобы получить каждый элемент. Но это не работает. Как я могу получить каждый элемент?

Обновление - больше кода

if (item is ValueTuple) //this does not work, but I can do a GetType and check the name
{
    object tupleValue;
    int nth = 1;
    while ((tupleValue = ((dynamic)item).GetType().GetProperty($"Item{nth}")) != null && //this does not work
        nth <= 8)      
    {
        nth++;
        //Do stuff
    }
}

Ответ 1

Это мое решение проблемы. Класс расширения, совместимый с PCL. Особая благодарность @dasblinkenlight и @Evk за то, что помогли мне!

public static class TupleExtensions
{
    private static readonly HashSet<Type> ValueTupleTypes = new HashSet<Type>(new Type[]
    {
        typeof(ValueTuple<>),
        typeof(ValueTuple<,>),
        typeof(ValueTuple<,,>),
        typeof(ValueTuple<,,,>),
        typeof(ValueTuple<,,,,>),
        typeof(ValueTuple<,,,,,>),
        typeof(ValueTuple<,,,,,,>),
        typeof(ValueTuple<,,,,,,,>)
    });

    public static bool IsValueTuple(this object obj) => IsValueTupleType(obj.GetType());
    public static bool IsValueTupleType(this Type type)
    {
        return type.GetTypeInfo().IsGenericType && ValueTupleTypes.Contains(type.GetGenericTypeDefinition());
    }

    public static List<object> GetValueTupleItemObjects(this object tuple) => GetValueTupleItemFields(tuple.GetType()).Select(f => f.GetValue(tuple)).ToList();
    public static List<Type> GetValueTupleItemTypes(this Type tupleType) => GetValueTupleItemFields(tupleType).Select(f => f.FieldType).ToList();    
    public static List<FieldInfo> GetValueTupleItemFields(this Type tupleType)
    {
        var items = new List<FieldInfo>();

        FieldInfo field;
        int nth = 1;
        while ((field = tupleType.GetRuntimeField($"Item{nth}")) != null)
        {
            nth++;
            items.Add(field);
        }

        return items;
    }
}

Ответ 2

Структуры не наследуются на С#, поэтому ValueTuple<T1>, ValueTuple<T1,T2>, ValueTuple<T1,T2,T3> и т.д. - это разные типы, которые не наследуют от ValueTuple как их базу. Следовательно, проверка obj is ValueTuple не выполняется.

Если вы ищете ValueTuple с аргументами произвольного типа, вы можете проверить, есть ли класс ValueTuple<,...,> следующим образом:

private static readonly Set<Type> ValTupleTypes = new HashSet<Type>(
    new Type[] { typeof(ValueTuple<>), typeof(ValueTuple<,>),
                 typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>),
                 typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>),
                 typeof(ValueTuple<,,,,,,>), typeof(ValueTuple<,,,,,,,>)
    }
);
static bool IsValueTuple2(object obj) {
    var type = obj.GetType();
    return type.IsGenericType
        && ValTupleTypes.Contains(type.GetGenericTypeDefinition());
}

Чтобы получить подпозиции на основе типа, вы могли бы использовать подход, который не особенно быстр, но должен сделать трюк:

static readonly IDictionary<Type,Func<object,object[]>> GetItems = new Dictonary<Type,Func<object,object[]>> {
    [typeof(ValueTuple<>)] = o => new object[] {((dynamic)o).Item1}
,   [typeof(ValueTuple<,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2}
,   [typeof(ValueTuple<,,>)] = o => new object[] {((dynamic)o).Item1, ((dynamic)o).Item2, ((dynamic)o).Item3}
,   ...
};

Это позволит вам сделать это:

object[] items = null;
var type = obj.GetType();
if (type.IsGeneric && GetItems.TryGetValue(type.GetGenericTypeDefinition(), out var itemGetter)) {
    items = itemGetter(obj);
}

Ответ 3

Относительно части вопроса "Как я могу получить каждый элемент?"...

Оба ValueTuple и Tuple реализуют ITuple, который имеет свойство length и свойство indexer. Таким образом, следующий код консольного приложения перечисляет значения в консоли:

// SUT (as a local function)
IEnumerable<object> GetValuesFromTuple(System.Runtime.CompilerServices.ITuple tuple) 
{
    for (var i = 0; i < tuple.Length; i++)
        yield return tuple[i];
}

// arrange
var valueTuple = (StringProp: "abc", IntProp: 123, BoolProp: false, GuidProp: Guid.Empty);

// act
var values = GetValuesFromTuple(valueTuple);

// assert (to console)
Console.WriteLine($"Values = '{values.Count()}'");

foreach (var value in values)
{
    Console.WriteLine($"Value = '{value}'");
}

Выход консоли:

Values = '4'
Value = 'abc'
Value = '123'  
Value = 'False'  
Value = '00000000-0000-0000-0000-000000000000'