-3
interface IFoo
{
    public ICollection<ICollection<string>> GetWords();
}

class Foo : IFoo
{
    public ICollection<ICollection<string>> GetWords()
    {
        return new List<List<string>>() { new List<string>() { "word" } };
    }
}

Is not allowed ("Cannot implicitly convert type...")

How can I save myself having to type cast everything when the interface is defined in terms of interfaces (generics?), and the implementation is of course using implementations of the types.

I feel like implementors should have choice in which implementation of ICollection they use to provide the functionality, so that's why I'd like the type in the interface to remain ICollection and callers can know they're working with some ICollection, but the interface shouldn't force implementors to use a certain implementation of ICollection nor should I have to deal with (redundant?) explicit subtype to supertype typecasts everywhere.

I'm using generic types and in my case I get the error:

Cannot implicitly convert type 'System.Collections.Generic.Dictionary<int, System.Collections.Generic.List>' to 'System.Collections.Generic.Dictionary<int, System.Collections.Generic.ICollection>'

I don't believe that a fabled lack of support for covariance is the explanation, and if it is please explain to me how. I don't agree with an explanation from the comments:

interface ... defines a method return type of SomeBase, and derived class overrides with a method returning SomeDerived. It is not currently supported

Because this fiddle (no nested generics) does exactly that successfully: dotnetfiddle.net/qkpxN8

And this page: https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance also says that

You can assign an instance of IEnumerable to a variable of type IEnumerable.

Serge
  • 40,935
  • 4
  • 18
  • 45
theonlygusti
  • 11,032
  • 11
  • 64
  • 119
  • Your second code example would compile by simply putting `public` at the start of the method signature. https://dotnetfiddle.net/qkpxN8 – gunr2171 Oct 12 '21 at 18:06
  • @RB. how is what I am doing anti-OOP? Isn't my question exactly Liskov's substitution principle? – theonlygusti Oct 12 '21 at 18:07
  • @theonlygusti The fact that implementors must conform to the interface specification is exactly why LSP holds true in this scenario - if implementations or subtypes could just choose whatever, LSP wouldn't be possible – Mathias R. Jessen Oct 12 '21 at 18:09
  • 1
    @theonlygusti the [mre] I've provided in my link does not have that error. Please provide a full [mre] showing such an error. – gunr2171 Oct 12 '21 at 18:10
  • 1
    Does this answer your question? [Does C# support return type covariance?](https://stackoverflow.com/questions/5709034/does-c-sharp-support-return-type-covariance) C# 9.0 will support the first syntax – Charlieface Oct 12 '21 at 18:13
  • Only on the second example, not the first – Charlieface Oct 12 '21 at 18:15
  • @RB. Not wrong if you take into account covariance – Charlieface Oct 12 '21 at 18:16
  • @Charlieface covariance is not what I want at all – theonlygusti Oct 12 '21 at 18:21
  • @mason that's exactly what I have but it doesn't work for me – theonlygusti Oct 12 '21 at 18:23
  • The problem is that `Dictionary` is not covariant! – Christian Held Oct 12 '21 at 18:23
  • @ChristianHeld that could be a good answer if it were true: "Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types" https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance – theonlygusti Oct 12 '21 at 18:25
  • @ChristianHeld That's not quite true: `TKey` and `TValue` are not variant, for fairly obvious reasons, but the Dictionary itself is variant against `ICollection`. This is not the same as covariance with generics, this is interface covariance – Charlieface Oct 12 '21 at 18:25
  • @Charlieface the page says "You can assign an instance of IEnumerable to a variable of type IEnumerable" – theonlygusti Oct 12 '21 at 18:27
  • Interface covariance is exactly what you have described: an interface or abstract class defines a method return type of `SomeBase`, and derived class overrides with a method returning `SomeDerived`, which is logically guaranteed to work, because `SomeBase : SomeDerived`. It is not currently supported. **This is not the same as generics variance, do not confuse them** – Charlieface Oct 12 '21 at 18:27
  • @Charlieface how can I work around it then? – theonlygusti Oct 12 '21 at 18:28
  • See the duplicate link above – Charlieface Oct 12 '21 at 18:29
  • @Charlieface https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance this page really reads to me as if everything I want is supported – theonlygusti Oct 12 '21 at 18:30
  • @Charlieface "interface ... defines a method return type of `SomeBase`, and derived class overrides with a method returning `SomeDerived` ... is not currently supported" seems to be disproven by counterexample by gunr's fiddle: https://dotnetfiddle.net/qkpxN8 – theonlygusti Oct 12 '21 at 18:32
  • @mason the issue is the code in the question doesn't compile, and I don't want to have to manually typecast every child collection – theonlygusti Oct 12 '21 at 18:33
  • No it will not. It will not allow you to override with an entirely different derived type. You can return the derived type, eg you could return a value which is `IEnumerable` if the return type is `IEnumerable`, but it won't let you actually change the return type of the function to that, the function itself remains with the base type. In @gunr2171 's example, a derived type is being returned (`List`) but the method remains `ICollection` (and this doesn't even need generics covariance) – Charlieface Oct 12 '21 at 18:36
  • @Charlieface in the fiddle https://dotnetfiddle.net/qkpxN8 the interface and signature specify `ICollection` and it returns `List`. – theonlygusti Oct 12 '21 at 18:38
  • @mason literally the code in the question – theonlygusti Oct 12 '21 at 18:44
  • 2
    There are plenty of scenarios which you could argue would fall well within Liskov's substitution principle, yet is not allowed in the .NET type system. And one of the rules in the .NET type system is that if your method is declared to return type X, you can either return a value of type X, or a value of a type descending from X. This does not include nested generic types, and `List>` does not inherit from `ICollection>`, nor is it that type. – Lasse V. Karlsen Oct 12 '21 at 18:45
  • @LasseV.Karlsen so How can I save myself having to type cast everything? – theonlygusti Oct 12 '21 at 18:47
  • 2
    Well, your types aren't even compatible. You can't return a `List>` typed into `ICollection>`. Why? Because this would allow the caller to add *any* type into the outer list, as long as that type implements `ICollection`, yet your underlying actual collection only accepts `List`. So, return a `List>`. – Lasse V. Karlsen Oct 12 '21 at 18:52
  • @LasseV.Karlsen thanks, that explains it, and the solution works well for me. Do you want to post it as an answer? – theonlygusti Oct 13 '21 at 14:39

1 Answers1

-1

This code was tested in visual studio and works properly.


public interface IFoo
{
    public ICollection<ICollection<string>> GetWords();
}

public class Foo : IFoo
{
    
    public ICollection<ICollection<string>> GetWords()
    {
        var list= new List< List<string>>() { new List<string> { "one", "two" }};
        ICollection<string> strings = new List<string>();
        ICollection<ICollection<string>> collection =new  List<ICollection<string>>();
         
        for (var i = 0; i < list.Count; i++)
        {
            for (var j = 0; j < list[i].Count; j++)
            {
                strings.Add(list[i][j]);

            }
             collection.Add(strings);
        }
    
        return  collection;
    }
}

It was tested in visual studio

static void Main()
{
    var foo = new Foo();
    var words = foo.GetWords();
     var json = System.Text.Json.JsonSerializer.Serialize(words);
}

json

[
["one","two"]
]
Serge
  • 40,935
  • 4
  • 18
  • 45