admin管理员组

文章数量:1122846

I want to create a read-only interface for my Box class. Box contains bananas. Bananas have a read-only interface IBananaRead.

The problem is, that in the box code I do all operations with List<Banana> but in the read only interface I want the other class to have access not to Banana but to IBananaReads. How to solve this type of situations?

class Box: IBoxRead
 {
List<Banana> bananas;
}

interface IBoxRead {
List<IBananaRead> bananas;
}

I could create a duplicate list of IBananaRead interfaces in the Box but that doesn't seem right due to duplication.

class Box: IBoxRead
 {
List<Banana> bananas;
List<IBananaRead> bananaReads;
}

interface IBoxRead {
List<IBananaRead> bananaReads;
}

The end goal is to have two types of interactions with the Box:

// A read-only class iterates over:
foreach (IBananaRead b in instanceOfBoxRead.bananas) 
{
  b.ReadTheBanana();
}

// Another class that has right to edit the bananas in the box iterates
// using the class Banana, not the readonly interface of IBananaRead:
foreach (Banana b in instanceOfBox.bananas) 
{
  b.EditTheBanana();
}

I want to create a read-only interface for my Box class. Box contains bananas. Bananas have a read-only interface IBananaRead.

The problem is, that in the box code I do all operations with List<Banana> but in the read only interface I want the other class to have access not to Banana but to IBananaReads. How to solve this type of situations?

class Box: IBoxRead
 {
List<Banana> bananas;
}

interface IBoxRead {
List<IBananaRead> bananas;
}

I could create a duplicate list of IBananaRead interfaces in the Box but that doesn't seem right due to duplication.

class Box: IBoxRead
 {
List<Banana> bananas;
List<IBananaRead> bananaReads;
}

interface IBoxRead {
List<IBananaRead> bananaReads;
}

The end goal is to have two types of interactions with the Box:

// A read-only class iterates over:
foreach (IBananaRead b in instanceOfBoxRead.bananas) 
{
  b.ReadTheBanana();
}

// Another class that has right to edit the bananas in the box iterates
// using the class Banana, not the readonly interface of IBananaRead:
foreach (Banana b in instanceOfBox.bananas) 
{
  b.EditTheBanana();
}
Share Improve this question asked Nov 22, 2024 at 8:43 luminousluminous 191 silver badge4 bronze badges 7
  • Looking at the last box: Do you expect the solution to be Thread-Safe? – Fildor Commented Nov 22, 2024 at 8:43
  • Something like this is possible: dotnetfiddle.net/KOWMEs but it wouldn't support concurrent (mutational) iteration of the wrapped list. – Fildor Commented Nov 22, 2024 at 8:53
  • 2 First of all, your IBoxRead interface cannot be compiled. – shingo Commented Nov 22, 2024 at 8:54
  • 3 Ask yourself whether exposing a list, which can be modified, is what you're actually trying to achieve here. Do you really want callers to be able to insert SomethingElseThatImplementsIBananaRead objects into this list? – Damien_The_Unbeliever Commented Nov 22, 2024 at 8:54
  • Ideallly, consumers wouldn't even know that there is a List<T> (whatever T actually is) inside the Box. – Fildor Commented Nov 22, 2024 at 8:56
 |  Show 2 more comments

2 Answers 2

Reset to default 3

Covariance and contravariance in generics | Microsoft Learn

A List<T> or IList<T> is, by definition, invariant. You can't pass it around as an IList<U> for either T : U or U : T.

If you could:

  • For T : U, you could insert an instance of V, where V : U, which would mean the List<T> would contain an item which was not an instance of T.
  • For U : T, you could have a List<U> containing items which are not instances of U.

In this case, you want a covariant list. That means you need to use the IReadOnlyList<T> interface, which is implemented by the List<T> class. You can happily return a List<T> as IReadOnlyList<U> when T : U.

However, a property of type List<T> cannot implicitly implement an interface property of type IReadOnlyList<T>, so you would need to implement that property explicitly. The explicit implementation can simply delegate to the List<T> property.

This would give you:

interface IBoxRead
{
    IReadOnlyList<IBananaRead> Bananas { get; }
}

class Box : IBoxRead
{
    public List<Banana> Bananas { get; }
    IReadOnlyList<IBananaRead> IBoxRead.Bananas => Bananas;
}

NB: This pattern would not prevent consumers of the IBoxRead interface from directly casting the property to List<Banana> and modifying the list. But by the same argument, they could also cast the IBoxRead instance to a Box, which would give them the same access.

Unless I'm missing something, all you need is this:

var boxList = new List<Box>();

// ...

var iBoxReadList = boxList.Cast<IBoxRead>().ToList();

That would need to be executed on demand, i.e. each time you need a list of read-only items, as it will not be updated along with the original list. You could declare a read-only property and create the new list in the getter.

That said, the second list should probably be read-only too, rather than just the items. Otherwise, the consumer could add items to the list to no good effect. In that case, you should probably do this:

var iBoxReadList = boxList.Cast<IBoxRead>().ToList().AsReadOnly();

That will create a ReadOnlyCollection<IBoxRead>.

本文标签: