0

I have a class that have a property (SearchResults) that need to be List<T>, where T is one of my search classes depend on a condition

public class SearchGeneralResponse : ActionResponse
{
    public IList<ISearchRecord> SearchResults { get; set; }


    public SearchGeneralResponse(MbsObjectType searchType)
    {
        if(searchType == MbsObjectType.SourceRepo) SearchResults = new List<SearchRecord>();
        if(searchType == MbsObjectType.BuildConfiguration) SearchResults = new List<SearchRecordBuild>();
    }
}

I can cast new SearchRecord to ISearchRecord. But when I do it with list

this.SearchResults = new List<SearchRecord>();

I get this error:

Cannot implicitly convert type System.Collections.Generic.List 'SearchRecord' to System.Collections.Generic.List 'ISearchRecord'

Here's my interface:

public interface ISearchRecord 
{

}

And one of the derived classes:

public class SearchRecord : ISearchRecord
{
    public string Name { get; set; }

    public string Summary { get; set; }

}

How can I create a List<T> property that can be initialized to a list of a class depending on a certain condition?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Adam Bahrani
  • 65
  • 3
  • 11
  • 1
    Why would you want to? Why not keep the list as `List` ? – Igor Aug 01 '19 at 17:25
  • Because I need to use different type of list depend on a responses from an API – Adam Bahrani Aug 01 '19 at 17:38
  • But *why*? What do you intend to do with this different type of list? Why does the type matter to you? Are you running into a problem that you have not yet shared in your question? – Igor Aug 01 '19 at 17:53
  • Generic type variance has been beaten to death on Stack Overflow. Please see e.g. marked duplicate. In your example, your goal is fundamentally flawed, because it would result in code that's not type-safe. If you can change from `IList` to `IReadOnlyList`, that might work in your case. There's not enough detail in your question to know, and it's not really a novel question anyway. – Peter Duniho Aug 01 '19 at 20:53

2 Answers2

2

Add a cast to your initializer:

this.SearchResults = new List<SearchRecord>().Cast<ISearchRecord>().ToList();
MikeH
  • 4,242
  • 1
  • 17
  • 32
  • Why not just `new List()`? – David Aug 01 '19 at 17:24
  • 1
    @David Depends on what the OP is really trying to do. If he's generating the list from another method and it returns `List` he'll have to cast the return value. – MikeH Aug 01 '19 at 17:26
  • 1
    Fair enough. Though the OP really should indicate that as part of the problem :) – David Aug 01 '19 at 17:27
2

Generics provide compile-time type safety, but in this case you're trying to tell the compiler that it should just trust you that there won't be run-time problems. Its job is to not trust you :)

Consider what would happen if this assignment were allowed and you did this:

IList<ISearchRecord> results;             // results is of type IList<ISearchRecord>
results = new List<SearchRecord>();       // but it holds a value of type List<SearchRecord>
results.Add(new SomeOtherSearchRecord()); // ERROR

Since the property SearchResults is of type IList<ISearchRecord>, any code which uses that property can assign any implementation of ISearchRecord to an element of that list. So it needs to always be ISearchRecord and not a more specific implementing type.

Step back and consider the semantics of what your code needs to do. Should SearchResults support any implementation of ISearchRecord? If it's always going to be assigned from SearchRecord then make it that specific type:

public IList<SearchRecord> SearchResults { get; set; }

Or, if it needs to be a list of ISearchRecord (so it can support other implementations) then you'd have to create the list of that type:

this.SearchResults = new List<ISearchRecord>();

Edit: Also, if your new List<>() is just a contrived example and you're actually getting the list from somewhere else, you still need to create a new list. Fortunately the references within that list can still be to the same objects, but the list itself needs to be the correct compile-time type. You could achieve this with:

this.SearchResults = someOtherList.Cast<ISearchRecord>().ToList();

This would create a new list object, of the correct type, containing the same elements as someOtherList.

David
  • 208,112
  • 36
  • 198
  • 279
  • This really gets to how the OP should look at the problem. Nice answer. – MikeH Aug 01 '19 at 17:30
  • How about using this, public IEnumerable SearchResults { get; set; } when I use this it eliminated the error – Adam Bahrani Aug 01 '19 at 17:42
  • Also, I'm getting the list from an API call, that call could return different types of list, and I'm trying to sum it up in interface and base on condition decided what type of list rather than creating 3 different classes – Adam Bahrani Aug 01 '19 at 17:43
  • @AdamBahrani: Likely because you can't add elements to an `IEnumerable<>`. I'm impressed that the compiler was smart enough, but it makes sense. The point is, you're kind of playing roulette with what type you want your property to be. Define the type based on what you need it to do. – David Aug 01 '19 at 17:43