0

I am creating a converter by implementing IMultiValueConverter with *Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

I am passing a List<(SomeEnumType, string)> tuple.

via MultiBinding and on the converter side I would like to cast but it throws a casting error.

I tried : var result = (List<(Enum, string)>)values[1];

but I got this casting issue:

'Unable to cast object of type 'System.Collections.Generic.List1[System.ValueTuple2[Vasco.Basics.Contracts.CoreConfigurations.Enums.ApplicationType,System.String]]' to type 'System.Collections.Generic.List1[System.ValueTuple2[System.Enum,System.String]]'.'

It is strange because If I pass only one element of SomeEnumType and try to case like (Enum)values[1] casting works well.

When I pass a List<SomeEnumType> and try to cast like (List<Enum>)values[1] does not work already.

Thank you in advance!

  • If it is a List of tuple why it is not declared like this: `List> tuple` ? – Jonathan Applebaum May 15 '21 at 09:41
  • Are you able to post more code so we can see what might be happening, please? To cast to a list you are better off using a Linq extension method, e.g. `values[1].ToList()`. – bfren May 15 '21 at 09:44
  • 1
    @jonathana in recent versions of C# (7+) this is the way to declare a tuple - https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples. – bfren May 15 '21 at 09:46
  • @bcg didn`t know that, Thanks for the update. – Jonathan Applebaum May 15 '21 at 09:48
  • Like jonathana said you should declare tuple like List> – zia khan May 15 '21 at 09:56
  • @ziakhan this is incorrect, it is absolutely fine (and much cleaner in my view) to declare a tuple like this: `List<(Enum, string)>` - see the link I posted above. – bfren May 15 '21 at 12:49

2 Answers2

2

When I pass a List and try to cast like (List)values1 does not work already.

You generally aren't allowed to cast generic collections like List<T> or IEnumerable<T> to other types. This comes down to how C# and the compiler handle generics and something called Covariance and contravariance. This is an incredible complicated topic, at least for me, so I won't bogg you down with the fine details.

Consider the following situation.

List<string> strings = new() { "Kitten", "Mouse", "horse" };
List<object> objs = strings;

This may seem pretty natural, especially if you try to explicitly cast the strings list such as (List<object>)strings, but this wont compile and that is a good thing! It protects you from doing silly things, like for example:

List<string> strings = new() { "Kitten", "Mouse", "horse" };
List<object> objs = strings;
objs.Add(1.29d);

this may seem like it's only tangentially related to you question, but this is really important, and is the exact reason you cant cast a collection to a different kind of collection, even if you know that they're very similar.

When we add that double to the objs list (assuming that this would compile, it doesn't), what were doing effectively is adding a double to a List<string> which would break everything about how strongly typed languages such as C# work.

It is strange because If I pass only one element of SomeEnumType and try to case like (Enum)values1 casting works well.

The reason you can do this, but not collections, is becuase with a single object the compiler can check to see if there is a valid conversion and do the conversion for you manually. Unlike with collections where the compiler, if it did the same thing as it did with single objects, it would add things to collections that may not match the type that was constrained when that collection was initialized.

Credit to John Skeet for this explanation, Ch4.4.1 ISBN 9781617294532

DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • 1
    Nice explanation. But note that _"it's not allowed to cast generic collections to other types"_ is not correct in general, because arrays are an exception of this rule(because you can't modify them). https://stackoverflow.com/a/17619183/284240 – Tim Schmelter May 15 '21 at 10:25
  • Yup you're totally correct! I tried to emphasize the generally part in the beginning, because with c# there are always weird exceptions! – DekuDesu May 15 '21 at 10:27
0

In general you cannot cast Lists like this - because what you are actually trying to do is cast each item in the list, rather than the list itself. Therefore you'd need to loop through and cast each item individually, like so:

var input = new List<(SomeEnumType, string)>();
// now add items to the input list

var result = new List<(Enum, string)>();
foreach (var element in input)
{
    result.Add(
        ((Enum)element.Item1, element.Item2)
    );
}

Remember, a tuple is not a single element but a wrapper for multiple elements, which each need casting.

Or, you could use a tool that allows you to 'map' types, e.g. Mapster or AutoMapper - I personally prefer Mapster.

using Mapster;

var input = new List<(SomeEnumType, string)>();
// now add items to the input list

var result = input.Adapt<List<(Enum, string)>>();
// Adapt<>() is an extension method provided by Mapster
bfren
  • 473
  • 3
  • 7
  • I tried to use foreach and cast each element works well only when the source is `List();` When I use `List<(SomeEnumType, string)>();` as source then the element can not be casted. – Zoltán Herczeg May 15 '21 at 15:22
  • That's because a tuple is actually two elements in one, so you would have to cast each element in the tuple as well - I'll update my answer to reflect this. I suggest exploring the mapping tools though as they will do what you want. – bfren May 16 '21 at 07:32