1

Suppose you have a list class like

class Words : List<Word>
{
  ...
}

and suppose you want to create one of these from an IEnumerable<Word> with a function like:

public static TList ToTypedList<TList, T>(this IEnumerable<T> list)
where TList : IList<T>, new()
{
  var listTyped = new TList();

  foreach (var item in list)
  {
     listTyped.Add(item);
  }

  return listTyped;
}

so that you can write:

Words words = someEnumerableOfWords.ToTypedList<Words, Word>();

It seems redundant to have to specify the Word type parameter, since someEnumerableOfWords is already of type IEnumerable<Word> and could be inferred, but doing, say, this:

Words words = someEnumerableOfWords.ToTypedList<Words>();

causes a compiler error.

Is there any way to accomplish the kind of type inference that I'm looking for?

Update: To clarify why I want to do this, it's not just about saving a few characters while typing, but also have a chainable, fluid interface:

var result = object.Child.MethodThatOutputsWords().ToTypedList<Words>().DoSomethingWithTheWords().DoSomethingElse();

The suggestion to just make another constructor that takes the list of words isn't perfect because it would require

var result = new Words(object.Child.MethodThatOutputsWords()).DoSomethingWithTheWords().DoSomethingElse();

and I find that much harder to read, because you can't just scan left to right, as in the first example.

Joshua Frank
  • 13,120
  • 11
  • 46
  • 95
  • I'm not sure I understand the code read quickly, but why not just use LINQ [ConvertAll](https://stackoverflow.com/questions/3555661/convert-list-of-one-type-to-list-of-another-type-in-c-sharp-3-5) or [Select](https://stackoverflow.com/questions/7075572/using-linq-to-convert-listu-to-listt) or [Cast](https://stackoverflow.com/questions/5115275/shorter-syntax-for-casting-from-a-listx-to-a-listy) depending on the case? –  Aug 13 '21 at 14:52
  • Assuming there is a way to create a `Words` from a `List` (possibly a constructor?) then why not do it after the fact? `new Words(someEnumerableOfWords.ToList())` – Wyck Aug 13 '21 at 14:53
  • I'd say you should [favour composition](https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) here and `words` should **contain** a List. Makes everything much easier. – Liam Aug 13 '21 at 15:17
  • @Wyck: I just like fluent and chainable interfaces. I might want to do `...ToList().DoSomething().DoSomethingElse()`, and having to use `new Words()` as a containing construction is harder to read, IMO. – Joshua Frank Aug 13 '21 at 17:40
  • @Liam: I've read that thread many times and I agree with much of it. But sometimes `Words` really is just a `List`, and I want to have a `Words` class mostly as an alias, and then I don't have to implement `IList` in every such list class. – Joshua Frank Aug 13 '21 at 17:43
  • For an alias, consider `using Words = System.Collections.Generic.List;` – Wyck Aug 13 '21 at 20:50
  • @Wyck: That's good, but the `using` statement needs to be written in every single file in the project that needs the alias, which is really inconvenient. – Joshua Frank Aug 14 '21 at 16:23

2 Answers2

2

In C#, there is no way to use only one type parameter and keep type safety (excluding solutions using Reflection).

Just add a constructor to the Words class having an IEnumerable<Word> parameter and pass it to the base constructor (List<T> happens to have such a constructor):

class Words : List<Word>
{
    public Words(IEnumerable<Word> words)
        : base(words)
    {
    }

    public Words() // Default constructor, allows you to create an empty list.
    {
    }
}

Then

var words = new Words(someEnumerableOfWords);
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
1

I agree with Olivier, that's the cleanest way. But if you would insist on a general extension method, you'll lose the type check for the inner elements. Try this:

public static TList ToTypedList<TList>(this IEnumerable list) where TList : IList, new()
{
    TList result = new TList();
    
    foreach (var element in list)
    {
        result.Add(element);
    }
    
    return result;
}
SmartK8
  • 2,616
  • 1
  • 29
  • 37