admin管理员组

文章数量:1026989

TL;DR If I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray(), from IEnumerable<T> to List<T> without calling .ToList(), etc. then what can I do if anything at all?


MINIMUM REPRODUCIBLE EXAMPLE

using System;
using System.Collections.Generic;
using System.Linq;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        string[] fruits = {"apple","mango","cherry"};
        GenericTest(fruits, out var fruits_onlyApples);
    }
    
    private static void GenericTest<T>(T collection, out T alteredCollection) where T:class,IEnumerable<string>{
        alteredCollection = (T)collection.Where(x=>x=="apple");
    }
}

Hey guys, I'm trying to write a method like GenericTest<T> above where, among other operations upon T, I can use .Where() from System.Linq to filter the collection into a copy by some predicate. The trouble is that, when the above code executes, I get an exception:

System.InvalidCastException: Specified cast is not valid.

I know what the problem is, this is because I'm doing a C-style cast from T to string[] when IEnumerable specifically has the .ToArray() method to achieve the transformation from IEnumerable to System.Array. Even though the return type of .Where() is the same as the generic type (T) that goes into its predicate, one still has to cast to T after using .Where() or else the code simply won't compile. I presume one has to do the cast because the where contracts used in the definition of .Where() do not match the contracts used in my GenericTest<T> method above.

What I want to know is what can I do about this? I don't want to call .ToArray() directly because then that defeats the point of using a generic type and creates the problem I'm trying to avoid of making one method over and over doing pretty much the same thing but for List<>, System.Array, etc.

Any ideas? Many thanks for reading : )

TL;DR If I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray(), from IEnumerable<T> to List<T> without calling .ToList(), etc. then what can I do if anything at all?


MINIMUM REPRODUCIBLE EXAMPLE

using System;
using System.Collections.Generic;
using System.Linq;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        string[] fruits = {"apple","mango","cherry"};
        GenericTest(fruits, out var fruits_onlyApples);
    }
    
    private static void GenericTest<T>(T collection, out T alteredCollection) where T:class,IEnumerable<string>{
        alteredCollection = (T)collection.Where(x=>x=="apple");
    }
}

Hey guys, I'm trying to write a method like GenericTest<T> above where, among other operations upon T, I can use .Where() from System.Linq to filter the collection into a copy by some predicate. The trouble is that, when the above code executes, I get an exception:

System.InvalidCastException: Specified cast is not valid.

I know what the problem is, this is because I'm doing a C-style cast from T to string[] when IEnumerable specifically has the .ToArray() method to achieve the transformation from IEnumerable to System.Array. Even though the return type of .Where() is the same as the generic type (T) that goes into its predicate, one still has to cast to T after using .Where() or else the code simply won't compile. I presume one has to do the cast because the where contracts used in the definition of .Where() do not match the contracts used in my GenericTest<T> method above.

What I want to know is what can I do about this? I don't want to call .ToArray() directly because then that defeats the point of using a generic type and creates the problem I'm trying to avoid of making one method over and over doing pretty much the same thing but for List<>, System.Array, etc.

Any ideas? Many thanks for reading : )

Share Improve this question asked Dec 11, 2024 at 0:20 Spring E. ThingSpring E. Thing 1757 bronze badges 6
  • 3 .Where() is never going to return T, it returns IEnumerable<string>. You'd probably need to special case how to create each type, in which case I'd just overload the method instead of trying to implement this in a generic way. Or just make it the callers problem and just return the IEnumerable<string>. – Jeremy Lakeman Commented Dec 11, 2024 at 0:25
  • @JeremyLakeman I had a feeling that's where this was going, I just figured since T and IEnumerable<string> would always end up being the same type in the context of what I've written that there shouldn't be any issue but I guess not. Oh well, I will concede and just do overloads, thank you all the same! – Spring E. Thing Commented Dec 11, 2024 at 0:28
  • 2 .Where always returns IEnumerable<T>, never List<T> or T[]. That would rather defeat the point of using enumerables. You call .ToList() or .ToArray() to materialise the items into an actual collection. – ProgrammingLlama Commented Dec 11, 2024 at 1:29
  • 2 What is an actual real world use case for this? Why is it important that the result is the same collection type? This pattern seem to go against the whole idea of LINQ. Have you considered using an interface like IReadOnlyList<T> instead? That way you do not need to care if it is a list or an array. – JonasH Commented Dec 11, 2024 at 9:02
  • "I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray()" - what makes you want that? I have the feeling it would be better to look at the underlying problem than fixing a sub-par solution to that. – Fildor Commented Dec 11, 2024 at 11:24
 |  Show 1 more comment

1 Answer 1

Reset to default 2

There are only a few limited cases where you can use .ToSomeCollection or similar anyway. So you might as well special-case them.

private static void GenericTest<T>(T collection, out T alteredCollection) where T : class, IEnumerable<string>
{
    var query = collection.Where(x => x == "apple");
    if (typeof(T) == typeof(string[])
        query = query.ToArray();
    else if (typeof(T) == typeof(List<string>))
        query = query.ToList();
    else if (typeof(T) == typeof(HashSet<string>))
        query = query.ToHashSet();
    else
        throw new UnsupportedException($"{typeof(T)} is unsupported");

    alteredCollection = (T)query;
}

Alternatively just create some fixed overloads

private static void Test(string[] collection, out string[] alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToArray();
}

private static void Test(List<string> collection, out List<string>  alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToList();
}

private static void Test(HashSet<string> collection, out HashSet<string> alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToHashSet();
}

private static IEnumerable<string> GenericTestCore<T>(T collection) where T : class, IEnumerable<string>
{
    return collection.Where(x => x == "apple");
}

Quite why you are using an out parameter rather than a normal return is not clear.

TL;DR If I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray(), from IEnumerable<T> to List<T> without calling .ToList(), etc. then what can I do if anything at all?


MINIMUM REPRODUCIBLE EXAMPLE

using System;
using System.Collections.Generic;
using System.Linq;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        string[] fruits = {"apple","mango","cherry"};
        GenericTest(fruits, out var fruits_onlyApples);
    }
    
    private static void GenericTest<T>(T collection, out T alteredCollection) where T:class,IEnumerable<string>{
        alteredCollection = (T)collection.Where(x=>x=="apple");
    }
}

Hey guys, I'm trying to write a method like GenericTest<T> above where, among other operations upon T, I can use .Where() from System.Linq to filter the collection into a copy by some predicate. The trouble is that, when the above code executes, I get an exception:

System.InvalidCastException: Specified cast is not valid.

I know what the problem is, this is because I'm doing a C-style cast from T to string[] when IEnumerable specifically has the .ToArray() method to achieve the transformation from IEnumerable to System.Array. Even though the return type of .Where() is the same as the generic type (T) that goes into its predicate, one still has to cast to T after using .Where() or else the code simply won't compile. I presume one has to do the cast because the where contracts used in the definition of .Where() do not match the contracts used in my GenericTest<T> method above.

What I want to know is what can I do about this? I don't want to call .ToArray() directly because then that defeats the point of using a generic type and creates the problem I'm trying to avoid of making one method over and over doing pretty much the same thing but for List<>, System.Array, etc.

Any ideas? Many thanks for reading : )

TL;DR If I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray(), from IEnumerable<T> to List<T> without calling .ToList(), etc. then what can I do if anything at all?


MINIMUM REPRODUCIBLE EXAMPLE

using System;
using System.Collections.Generic;
using System.Linq;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        string[] fruits = {"apple","mango","cherry"};
        GenericTest(fruits, out var fruits_onlyApples);
    }
    
    private static void GenericTest<T>(T collection, out T alteredCollection) where T:class,IEnumerable<string>{
        alteredCollection = (T)collection.Where(x=>x=="apple");
    }
}

Hey guys, I'm trying to write a method like GenericTest<T> above where, among other operations upon T, I can use .Where() from System.Linq to filter the collection into a copy by some predicate. The trouble is that, when the above code executes, I get an exception:

System.InvalidCastException: Specified cast is not valid.

I know what the problem is, this is because I'm doing a C-style cast from T to string[] when IEnumerable specifically has the .ToArray() method to achieve the transformation from IEnumerable to System.Array. Even though the return type of .Where() is the same as the generic type (T) that goes into its predicate, one still has to cast to T after using .Where() or else the code simply won't compile. I presume one has to do the cast because the where contracts used in the definition of .Where() do not match the contracts used in my GenericTest<T> method above.

What I want to know is what can I do about this? I don't want to call .ToArray() directly because then that defeats the point of using a generic type and creates the problem I'm trying to avoid of making one method over and over doing pretty much the same thing but for List<>, System.Array, etc.

Any ideas? Many thanks for reading : )

Share Improve this question asked Dec 11, 2024 at 0:20 Spring E. ThingSpring E. Thing 1757 bronze badges 6
  • 3 .Where() is never going to return T, it returns IEnumerable<string>. You'd probably need to special case how to create each type, in which case I'd just overload the method instead of trying to implement this in a generic way. Or just make it the callers problem and just return the IEnumerable<string>. – Jeremy Lakeman Commented Dec 11, 2024 at 0:25
  • @JeremyLakeman I had a feeling that's where this was going, I just figured since T and IEnumerable<string> would always end up being the same type in the context of what I've written that there shouldn't be any issue but I guess not. Oh well, I will concede and just do overloads, thank you all the same! – Spring E. Thing Commented Dec 11, 2024 at 0:28
  • 2 .Where always returns IEnumerable<T>, never List<T> or T[]. That would rather defeat the point of using enumerables. You call .ToList() or .ToArray() to materialise the items into an actual collection. – ProgrammingLlama Commented Dec 11, 2024 at 1:29
  • 2 What is an actual real world use case for this? Why is it important that the result is the same collection type? This pattern seem to go against the whole idea of LINQ. Have you considered using an interface like IReadOnlyList<T> instead? That way you do not need to care if it is a list or an array. – JonasH Commented Dec 11, 2024 at 9:02
  • "I want to cast from IEnumerable<T> to System.Array<T> without calling .ToArray()" - what makes you want that? I have the feeling it would be better to look at the underlying problem than fixing a sub-par solution to that. – Fildor Commented Dec 11, 2024 at 11:24
 |  Show 1 more comment

1 Answer 1

Reset to default 2

There are only a few limited cases where you can use .ToSomeCollection or similar anyway. So you might as well special-case them.

private static void GenericTest<T>(T collection, out T alteredCollection) where T : class, IEnumerable<string>
{
    var query = collection.Where(x => x == "apple");
    if (typeof(T) == typeof(string[])
        query = query.ToArray();
    else if (typeof(T) == typeof(List<string>))
        query = query.ToList();
    else if (typeof(T) == typeof(HashSet<string>))
        query = query.ToHashSet();
    else
        throw new UnsupportedException($"{typeof(T)} is unsupported");

    alteredCollection = (T)query;
}

Alternatively just create some fixed overloads

private static void Test(string[] collection, out string[] alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToArray();
}

private static void Test(List<string> collection, out List<string>  alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToList();
}

private static void Test(HashSet<string> collection, out HashSet<string> alteredCollection)
{
    var query = GenericTestCore(collection);
    alteredCollection = query.ToHashSet();
}

private static IEnumerable<string> GenericTestCore<T>(T collection) where T : class, IEnumerable<string>
{
    return collection.Where(x => x == "apple");
}

Quite why you are using an out parameter rather than a normal return is not clear.

本文标签: cCasting IEnumerableltTgt to Arraylistetc genericallyStack Overflow