0

Given a class and a subclass

public class Base
{
  public string Name { get; set; }
}

public class Derived : Base
{
  public string Extra {get; set; }
}

and a generic list

var list = new List<Base>();

I want to prevent this:

var b = new Base { Name = "base" };
var d = new Derived { Name = "derived", Extra = "stuff" };

list.Add(b); // this is good
list.Add(d); // this is BAD

The reason for wanting to prevent this is the list will be serialized in a way that loses type information, then reserialized to List<Base>. Any items that derive frome Base will require downcasting to a type unknown by the deserializer (I certainly don't want use reflection to find a class that inherits from Base and has an 'Extra' property). Perhaps I will wrestle with approaches to solve that and that may lead to another question. But for now, can I avoid the problem by preventing derived objects from being added to a generic list?

biscuit314
  • 2,384
  • 2
  • 21
  • 29
  • They _may_ be useful...How to put disparate objects in a list and serialize/deserialize them and preserve all type information: https://weblogs.asp.net/stevewellens/serializing-arraylists-of-disparate-objects-to-xml-strings – Steve Wellens Sep 18 '14 at 14:42
  • 2
    Even if you could, you shouldn't: http://en.wikipedia.org/wiki/Liskov_substitution_principle – Mephy Sep 18 '14 at 14:56
  • @Mephy: if I understand how Liskov applies here, perhaps the answer is to upcast any derived objects that were added before serializing. This would strip out `Extra` in this example. Then deserialization would only see a uniform list. That makes a lot of sense. – biscuit314 Sep 18 '14 at 15:19

4 Answers4

3
public class UberStrongTypedList<T> : IList<T>
{
    private readonly IList<T> _list = new List<T>();

    public void Add(T item)
    {
        if (item.GetType() == typeof (T))
            _list.Add(item);
        else
            ;//some error
    }

    public void Clear()
    {
        _list.Clear();
    }

    //SNIP...
}
decPL
  • 5,384
  • 1
  • 26
  • 36
1

Why not a simple check:

if(!obj is Derived)
{
   list.Add(obj);
}

If you want to compare against exactly the base class then you can do:

if(obj != null && obj.GetType() == typeof(Base))
{
    list.Add(obj);
}

Remember, these checks can't prevent code without these checks, to add child class object to the list. Your other option is to create your Custom List<T> class, deriving from List<T> and supply a new Add method as explained in this answer.

Community
  • 1
  • 1
user2711965
  • 1,795
  • 2
  • 14
  • 34
  • 1
    Straightforward solution. But what if OP have a lot of classes derived from base or such classes will be created in future? – Andrey Korneyev Sep 18 '14 at 14:42
  • @AndyKorneyev, that is a valid point. I also update my answer against exact comparison of base class. – user2711965 Sep 18 '14 at 14:45
  • 1
    None of these solutions prevent someone from adding a derived type to the `List`. Some other code would still be able to add a derived type to the list. – Servy Sep 18 '14 at 14:47
  • @Servy, off course you can't prevent that without the exact type check, or having a custom list with `new` Add method. – user2711965 Sep 18 '14 at 14:48
  • 1
    @user2711965 Shadowing the `Add` methods doesn't prevent access to the method, it just requires casting the type as a base type/interface. And yes, there *are* ways to *actually* prevent adding a derived type, and that is to encapsulate the collection instead of inheriting from it, which enables you to provided exactly the behavior that is desired, without leaving holes. – Servy Sep 18 '14 at 14:54
1

No, it is not possible using the C# type system. The entire premise of inheritance is that derived objects can be treated as if they are an instance of any of their base types (or interfaces).

You could create a new collection that, when an item tries to be added, does a runtime check to verify if the type is an exact match for the collections generic type, but that's about the best you can do. You aren't going to be able to get static type verification of this constraint.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I accepted this answer because, though it wasn't clear in my question, I wanted to know if there was a declarative way to accomplish this. Thank you to those who suggested the custom list/check on Add() approach (thanks to @user2711965 for being the first, and to ''decPL'' for providing a sample). Some version of this may well be what I'll do, but as ''Servy'' pointed out in another comment, simply subclassing a List doesn't prevent access. Encapsulating the (as with decPL's) fixes the container to a single type (well, unless I genericize the container too... hmmmm). – biscuit314 Sep 18 '14 at 15:12
-1

Subclass a list:

private class Parent
{
    public int ID { get; set; }
}

private class Child : Parent
{

}

private class MyList : List<Parent>
{
    public new void Add(Parent item)
    {
        //runtime type check in case a Parent-derived object reaches here
        if (item.GetType() == typeof(Parent))
            base.Add(item);
        else
            throw new ArgumentException("List only supports Parent types");
    }

    [Obsolete("You can't add Child types to this list", true)]
    public void Add(Child item)
    {
        throw new NotImplementedException();
    }
}

static void Main(string[] args)
{
    var list = new MyList();
    list.Add(new Parent() { ID = 1 });
    list.Add(new Child() { ID = 2 }); //This gives a compiler error
}

Note that the Obsolete attribute with the true parameter is what causes the compiler error.

user2023861
  • 8,030
  • 9
  • 57
  • 86
  • 1
    Now you add a new class `Child2 : Parent`, and `list.Add(new Child2() { ID = 3});` will compile correctly. – Mephy Sep 18 '14 at 14:55
  • 1
    This still enables `list.Add((Parent)new Child() { ID = 2 });` or `((List)list).Add(new Child() { ID = 2 });` – Servy Sep 18 '14 at 14:59
  • That's true. If the OP has tight control over the code and can ensure there's no `Child2`, the OP will benefit from my solution as it gives a compile-time error. I can update my answer to include a type-check. – user2023861 Sep 18 '14 at 14:59