-1

I have several classes that exhibit a inheritance structure:

public class BaseClass
{
   Guid ID {get;set;}
}

public class LeafType : BaseClass{ /* omitted */}
public class OtherLeafType : BaseClass{ /* omitted */}    

public class Node : BaseClass
{
   public List<LeafType> FirstLeaves {get;set;}
   public List<OtherLeafType > SecondLeaves {get;set;}

   public ???? AllLeaves {get;} //Returns all items in both FirstLeaves and SecondLeaves
}

In the example above, Node has two collections, whose elements derive from BaseClass. Does .Net have a collection that can combine these two collections and automatically update when either FirstLeaves or SecondLeaves changes? I have found the class System.Windows.Data.CompositeCollection, but it is in PresentationFramework, which to me indicates that it is intended for UI purposes. My class Node lives in an assembly that has nothing to do with the UI, so CompositeCollection looks like a bad fit. Is there any other class that would serve a similar purpose?

Update 1: Looking at the answers so far, it seems that my question was not clearly formulated: CompositeCollection Enables multiple collections and items to be displayed as a single list, but I was wondering if the .Net framework supplies a type with similar functionality that is not related to the GUI. If not, then I will roll my own solution, which looks very much like the answer by @Erik Madsen

SimonAx
  • 1,176
  • 1
  • 8
  • 32
  • what operations do you need to support on this type, and what semantics should each of those operations have? – Servy Mar 21 '18 at 20:52
  • Well, first, AllLeaves could just be IEnumerable. What you'd have to do would be to create a custom event that would be raise whenever a change happens to eiher one of your collection and make sure to update AllLeaves – Kevin Avignon Mar 21 '18 at 20:54
  • 4
    `FirstLeaves.Concat(SecondLeaves)`? – mjwills Mar 21 '18 at 20:55
  • @mjwills It won't work while iterating this enumerable and trying to add something to those lists so it's save only in functional code – arekzyla Mar 21 '18 at 21:19
  • @arekzyla As soon as you introduce multiple threads, **most** solutions will cease to work (since `List` is not always thread-safe). _Unless SimonAx confirms that is a constraint, I think we can safely assume this is a single threaded concern._ – mjwills Mar 21 '18 at 21:20
  • @mjwills What about `foreach (var item in node.AllLeaves) node.FirstLeaves.Add(new LeafType())` ? Of course this code does not make sense but it will throw an exception. – arekzyla Mar 21 '18 at 21:23
  • `List` isn't safe to be used from multiple threads, so it's *impossible* to create a wrapper over two lists that provides a safe proxy in a multithreaded contexted because the underlying list doesn't support it. The real question is, as mentioned, what operations it needs to support. Does it need to be able to add items, remove them, get or set them by index, iterate it, etc. What all operations it needs to support, and how those operations need to behave, will determine how you actually implement such a proxy. – Servy Mar 21 '18 at 21:23
  • 1
    @arekzyla If that was a concern (and thanks for raising it), `FirstLeaves.OfType().Concat(SecondLeaves).ToList()` (to get a point in time snapshot). Honestly though, the _consumer_ of the `IEnumerable` should be doing the `ToList`). _As I said earlier though, it won't solve all threading issues._ – mjwills Mar 21 '18 at 21:24
  • @arekzyla The question is asking to provide a *new object* that itself has the appearance of being the combination of these two lists, that stays up to date with changes. That is accomplished by just adding the items from one to the other. – Servy Mar 21 '18 at 21:24
  • @mjwills Did you mean to put that `ToList` outside the `Concat` and not inside? – NetMage Mar 21 '18 at 21:27
  • I suspect either would work @NetMage. If you put it inside you would need two `ToLists` (one for each `List`). – mjwills Mar 21 '18 at 21:29
  • @arekzlya That could be what it means, definitely. The OP needs to clarify if they are concerned about that or if it is something else. For example, when iterating over `AllLeaves` are they expecting to see new `FirstLeaves` that were added during that iteration? Or do they expect that if they add something to `FirstLeaves` now that it will appear in the _next_ iteration over it? The first requirement is surprisingly hard (especially if you are at position 3 in iterating over the list and now someone adds something _earlier_ in the list - should it be included?). – mjwills Mar 21 '18 at 21:33
  • @SimonAx Do you particularly need `List` rather than `ConcurrentBag`? – mjwills Mar 21 '18 at 21:34
  • 1
    @arekzyla I have a solution for that exact problem [here](https://stackoverflow.com/a/24808234/1159478) – Servy Mar 21 '18 at 21:35
  • @mjwills If you wanted that behavior, of seeing items added earlier in the iteration, I don't imagine it would be a particularly complex change from the solution I linked in my previous comment. You'd just need to keep track of items added earlier (there's already a case statement for that exact situation) and not shown and list through them all after you reached the end of the underlying collection (or display them immediately before moving on, or whatever else you wanted to do instead). – Servy Mar 21 '18 at 21:38
  • Thanks @Servy. It is likely overkill, since I doubt the OP needs it. I wasn't saying it was impossible - just surprisingly hard (i.e. edge cases that most people aren't thinking about - for example in this case the order would be inconsistent between calls). – mjwills Mar 21 '18 at 21:39
  • 1
    @mjwills I agree it's unlikely to be needed. I provided that original answer more to prove it was possible than because I think it's actually a good thing to actually be in the situation where that's necessary (outside of some rare exceptions). – Servy Mar 21 '18 at 21:41

2 Answers2

1

I recommend using an iterator. It's not a collection but can converted to a collection via Linq's ToList() extension method.

The iterator provides a live view of the collection contents. You'll need to test what happens if the underlying collections are mutated while you're iterating through the IEnumerable. But generally this is considered bad practice.

public IEnumerable<BaseClass> AllLeaves
{
    get
    {
        foreach (LeafType firstLeaf in FirstLeaves)
        {
            yield return firstLeaf;
        }    
        foreach (OtherLeafType secondLeaf in SecondLeaves)
        {
            yield return secondLeaf;
        }
    }
}


public List<BaseClass> AllLeavesList()
{
    return AllLeaves.ToList();
}
Erik Madsen
  • 497
  • 6
  • 17
  • What is the benefit of that vs LINQ's `Concat`? – mjwills Mar 21 '18 at 22:58
  • @mjwills I also tried `FirstLeaves.Concat(SecondLeaves)` but Concat cannot infer the type. It can be fixed though, for example by using the following `FirstLeaves.Concat(SecondLeaves.Cast)` – SimonAx Mar 22 '18 at 07:58
  • 2
    That is correct @SimonAx. I believe I gave much the same suggestion in the comments against your question. Note that @Tronald's implementation below will be slightly faster than `AllLeavesList` here. You understand, with both of the solutions here, that they are not thread safe? _So you can't use these methods while adding items to it._ – mjwills Mar 22 '18 at 08:07
  • @mjwills yet my answer still got downvoted haha. Gotta love SO. Good note regarding thread safety! – Tronald Mar 22 '18 at 12:19
0

I believe concating one list to another may not work in your case as they are declared as different classes (even though they inherit the Base class). I would return a newly combined list.

public List<BaseClass> AllLeaves
{
     get
     {
        List<BaseClass> l = new List<BaseClass>();
        l.AddRange(FirstLeaves);
        l.AddRange(SecondLeaves);
        return l;
     }
 }
Tronald
  • 1,520
  • 1
  • 13
  • 31
  • That provides a *snapshot* of those two lists at that point in time. The question specifically asked for the returned object to reflect the values of the lists as they change over time. – Servy Mar 21 '18 at 21:26
  • Based on his code comment it sounded like he just wanted an updated list every time it's called? Guess I'm reading into wrong... – Tronald Mar 21 '18 at 21:37
  • 2
    If you did want to do it this way, consider using `List l = new List(FirstLeaves.Count + SecondLeaves.Count);` to reduce memory allocations. – mjwills Mar 21 '18 at 21:38