17

I have a use case where I need to check if a value is a C# 7 ValueTuple, and if so, loop through each of the items. I tried checking with obj is ValueTuple and obj is (object, object) but both of those return false. I found that I could use obj.GetType().Name and check if it starts with "ValueTuple" but that seems lame to me. Any alternatives would be welcomed.

I also have the issue of getting each item. I attempted to get Item1 with the solution found here: How do I check if a property exists on a dynamic anonymous type in c#? but ((dynamic)obj).GetType().GetProperty("Item1") returns null. My hope was that I could then do a while to get each item. But this does not work. How can I get each item?

Update - more code

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
    }
}
James Esh
  • 2,219
  • 1
  • 24
  • 41
  • 1
    ValueTuple is a Structure, that's why you need GetType(). Can you post more code ? – farbiondriven Oct 12 '17 at 10:53
  • Calling `((dynamic)item).GetType().GetProperties()` returns an empty array... :( – James Esh Oct 12 '17 at 11:12
  • 3
    Those `Item1` `Item2` etc are not properties - they are fields. So you have to do `GetType().GetField("Item1")...`. Casting to dynamic is not needed. – Evk Oct 12 '17 at 11:14
  • 3
    I can't help but think that this might be an X-Y problem. – Matthew Watson Oct 12 '17 at 12:23
  • You may want to consider having a specific method to deal with your type restriction. You can then make it a generic with a restriction like `where T : struct`. – BurnsBA Oct 12 '17 at 14:11
  • 2
    Also, "Importantly, the tuple field names aren't part of the runtime representation of tuples, but are tracked only by the compiler. As a result, the field names will not be available to a 3rd party observer of a tuple instance - such as reflection or dynamic code." https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md#name-erasure-at-runtime- – BurnsBA Oct 12 '17 at 14:15

5 Answers5

13

Structures do not inherit in C#, so ValueTuple<T1>, ValueTuple<T1,T2>, ValueTuple<T1,T2,T3> and so on are distinct types that do not inherit from ValueTuple as their base. Hence, obj is ValueTuple check fails.

If you are looking for ValueTuple with arbitrary type arguments, you can check if the class is ValueTuple<,...,> as follows:

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());
}

To get sub-items based on the type you could use an approach that is not particularly fast, but should do the trick:

static readonly IDictionary<Type,Func<object,object[]>> GetItems = new Dictionary<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}
,   ...
};

This would let you do this:

object[] items = null;
var type = obj.GetType();
if (type.IsGeneric && GetItems.TryGetValue(type.GetGenericTypeDefinition(), out var itemGetter)) {
    items = itemGetter(obj);
}
Carsten Løvbo Andersen
  • 26,637
  • 10
  • 47
  • 77
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
9

Regarding the part of the question "How can I get each item?"...

Both ValueTuple and Tuple implement ITuple, which has a length property and an indexer property. So a the following console app code lists the values to the console:

// 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}'");
}

Console output:

Values = '4'
Value = 'abc'
Value = '123'  
Value = 'False'  
Value = '00000000-0000-0000-0000-000000000000'
Peter
  • 971
  • 11
  • 12
6

This is my solution to the problem. A PCL compatible extension class. Special thanks to @dasblinkenlight and @Evk for helping me out!

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;
    }
}
James Esh
  • 2,219
  • 1
  • 24
  • 41
  • better solution is solved here: https://stackoverflow.com/questions/43341177/how-to-iterate-over-tuple-items – greektreat Apr 08 '22 at 14:18
2

If you dont like building hash tables upfront, something like this will work:

public static bool IsTupleType(this Type type)
{
    return typeof(ITuple).IsAssignableFrom(type);
}

public static bool IsValueTupleType(this Type type)
{
    return type.IsValueType && type.IsTupleType();
}

public static bool IsReferenceTupleType(this Type type)
{
    return type.IsClass && type.IsTupleType();
}

Efficient, and easier to read and maintain.

nawfal
  • 70,104
  • 56
  • 326
  • 368
0

hackish one liner

type.Name.StartsWith("ValueTuple`")

(can be extended to check the digit at the end)

kofifus
  • 17,260
  • 17
  • 99
  • 173
  • 2
    This was already stated in the question: _I found that I could use obj.GetType().Name and check if it starts with "ValueTuple" but that seems lame to me._ – Lance U. Matthews Nov 10 '21 at 04:56
  • Ah I see sorry I missed that, tbh while hackish it is probably totally solid – kofifus Nov 10 '21 at 11:54