0

I'm developing a library for developers where they have to create a class that inherits from a class I created.

This base class essentially manages an array of objects for the developer, however the developer gets to specify the type of these objects they want the base class to manage.

So the developer essentially just tells the base class to create an array, then only has read only access to that array. The base class will (depending on the state of the application) add or remove objects from the array.

I'm stuck at finding the right data type to store such a thing. I've tried ref and out but that got me nowhere. The closest I got was with a Dictionary but that idea fell apart because C# is actually just copying the value into the dictionary instead of referencing or pointing to it.

Here is a quick example I threw together:

    public static void Main()
    {
        Derived d = new Derived();
        d.InitBase();
        d.Init();
        d.CheckArray();
        d.AddElement<GenericObject>(new GenericObject{ i = 2 });
        d.CheckArray();
    }

    public class Base {
        Dictionary<Type, List<object>> ArrayReferences;

        public void InitBase() {
             ArrayReferences = new Dictionary<Type, List<object>>();
        }

        protected ReadOnlyCollection<T> RegisterArray<T>() {
            List<object> managedArray = new List<object>();
            ArrayReferences.Add(typeof(T), managedArray);
            return Array.AsReadOnly(managedArray.Select(s => (T)s).ToArray());
        }

        public void AddElement<T>(T obj) {
            ArrayReferences[typeof(T)].Add(obj);
        }

        public void RemoveElement<T>(T obj) {
            ArrayReferences[typeof(T)].Remove(obj);
        }
    }

    public class Derived: Base {
        ReadOnlyCollection<GenericObject> arr;
        public void Init() {
             arr = RegisterArray<GenericObject>();
        }
        public void CheckArray() {
            Console.WriteLine(arr.Count());
        }
    }

    public class GenericObject {
        public int i = 0;
    }

Output:

0
0

Dictionary obviously doesn't store the values as references like I want it to. So what other technique does C# have or is this simply not possible? Also not sure how many issues unsafe will cause me so I'm scared going that route.

FanManPro
  • 1,076
  • 1
  • 13
  • 31
  • 3
    Favor composition over inheritance. Rather than making a collection that someone inherits from in order to use, make your collection sealed, and let people create an instance of it to use that collection. Your derived classes aren't changing the behavior of the type, they're just using it. Also don't create methods to initialize an object. The object should be initialized in its constructor. – Servy Mar 05 '19 at 22:15
  • 2
    When you get arr in your Derived class, it is created a whole new array of objects at the point you call RegisterArray. You are adding objects fine to your dictionary, but they're not getting added to that array as it was just a snapshot in time. A snap shot of the current (empty in this case) set of references (if a reference type). – monty Mar 05 '19 at 22:41
  • Questions: Why do you want a derived class (as opposed to encapsulation)? and how does your "derived" (or parent if you go that way) want to use those snapshots? – monty Mar 05 '19 at 22:45
  • @monty The base class will exist in a dll which the developer shouldn't have access to. I the developer to create their own derived class where they will have more code related to the state the collection in the base class. – FanManPro Mar 06 '19 at 00:14
  • @Servy will give that a shot. That's a little out of the box thinking for me but if I understand you correctly, I think it will do the job. – FanManPro Mar 06 '19 at 00:27
  • In `RegisterArray` when you do `return ... .ToArray());` the `ToArray` is creating a new array and returning it as read only - you aren't returning a reference to `managedArray` after processing it with LINQ(btw, LINQ includes `Cast()` for what you are doing with `Select`). Make `Base` generic and dump the `Type` `Dictionary` for just a `managedArray` (which makes clear that a different `ArrayReferences` (its not `static`) will be created for each derived class). – NetMage Mar 06 '19 at 18:53

3 Answers3

1

While I think there are better ways of handling this issue, this can be done.

Instead of storing a List<object> reference, which isn't compatible with a List<T>, store an object. Use a static in Base to hold the Dictionary so there is one Dictionary for all derived classes.

public static void Main() {
    var d = new Derived();
    d.CheckCollection("d before AddElement");
    d.AddElement(new GenericObject { i = 2 });
    d.CheckCollection("d after AddElement");
    Console.WriteLine($"ListCount = {Base.ListCount}");

    var d2 = new Derived2();
    d2.CheckCollection("d2 before AddElement");
    d2.AddElement(new GenericObject2 { i = 4 });
    d2.AddElement(new GenericObject2 { i = 5 });
    d2.CheckCollection("d2 after AddElement");
    Console.WriteLine($"ListCount = {Base.ListCount}");
}

public class Base {
    static Dictionary<Type, object> ListReferences = new Dictionary<Type, object>();

    public static int ListCount => ListReferences.Count();

    protected ReadOnlyCollection<T> RegisterList<T>() {
        var managedList = new List<T>();
        ListReferences.Add(typeof(T), managedList);
        return managedList.AsReadOnly();
    }

    public void AddElement<T>(T obj) {
        ((List<T>)ListReferences[typeof(T)]).Add(obj);
    }

    public void RemoveElement<T>(T obj) {
        ((List<T>)ListReferences[typeof(T)]).Remove(obj);
    }
}

public class Derived : Base {
    ReadOnlyCollection<GenericObject> roc;
    public Derived() {
        roc = RegisterList<GenericObject>();
    }

    public void CheckCollection(string msg) {
        Console.WriteLine(msg);
        Console.WriteLine(roc.Count());
    }
}

public class Derived2 : Base {
    ReadOnlyCollection<GenericObject2> roc;
    public Derived2() {
        roc = RegisterList<GenericObject2>();
    }

    public void CheckCollection(string msg) {
        Console.WriteLine(msg);
        Console.WriteLine(roc.Count());
    }
}

public class GenericObject {
    public int i = 0;
}

public class GenericObject2 {
    public int i = 0;
}

PS Also, don't name methods and variables with "array" when you are using Lists.

NetMage
  • 26,163
  • 3
  • 34
  • 55
0

The following code you've written makes a copy of your list at the time you created it - so it is always empty, no matter what you add to the list afterwards.

List<object> managedArray = new List<object>();
ArrayReferences.Add(typeof(T), managedArray);
return Array.AsReadOnly(managedArray.Select(s => (T)s).ToArray());

Here is how you should write your code to get what you want:

public static void Main()
{
    Derived d = new Derived();
    Console.WriteLine(d.AsReadOnly().Count);
    d.AddElement(new GenericObject { i = 2 });
    Console.WriteLine(d.AsReadOnly().Count);
}

public class Base<T>
{
    List<T> _items = new List<T>();

    public ReadOnlyCollection<T> AsReadOnly()
    {
        return Array.AsReadOnly(_items.ToArray());
    }

    public void AddElement(T obj)
    {
        _items.Add(obj);
    }

    public void RemoveElement(T obj)
    {
        _items.Remove(obj);
    }
}

public class Derived : Base<GenericObject>
{
}

public class GenericObject
{
    public int i = 0;
}

That outputs:

0
1

Now, it's worth considering that List<T> already has a AsReadOnly() method, so you could simply write this:

public static void Main()
{
    var d = new List<GenericObject>();
    Console.WriteLine(d.AsReadOnly().Count);
    d.Add(new GenericObject { i = 2 });
    Console.WriteLine(d.AsReadOnly().Count);
}

public class GenericObject
{
    public int i = 0;
}

That works too.


Here's how you should do this to hold more than one list at a time. There's no need for inheritance.

public static void Main()
{
    Repository r = new Repository();
    Console.WriteLine(r.AsReadOnly<GenericObject>().Count);
    r.AddElement<GenericObject>(new GenericObject { i = 2 });
    Console.WriteLine(r.AsReadOnly<GenericObject>().Count);
}

public class Repository
{
    private Dictionary<Type, object> _references = new Dictionary<Type, object>();

    private void Ensure<T>()
    {
        if (!_references.ContainsKey(typeof(T)))
        {
            _references[typeof(T)] = new List<T>();
        }
    }

    public ReadOnlyCollection<T> AsReadOnly<T>()
    {
        this.Ensure<T>();
        return (_references[typeof(T)] as List<T>).AsReadOnly();
    }

    public void AddElement<T>(T obj)
    {
        this.Ensure<T>();
        (_references[typeof(T)] as List<T>).Add(obj);
    }

    public void RemoveElement<T>(T obj)
    {
        this.Ensure<T>();
        (_references[typeof(T)] as List<T>).Remove(obj);
    }
}

public class GenericObject
{
    public int i = 0;
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • I see what you're doing here. However there will be more than one of these arrays that the base class will manage and they will all have their own generic type. So I cannot make the Base class generic the way you have it: `public class Base`. – FanManPro Mar 06 '19 at 00:21
  • 1
    @FanusduToit - The `Base` class as **you have defined it** can only handle one array - that's the nature of inheritance. Each time you inherit, as in `class Derived : Base`, and you create a new instance of `Derived` then you are getting an associated instance of `Dictionary> ArrayReferences;`. If you want to have a single `Dictionary>` then you have to avoid inheritance and just have a single `Repository` class that replaces your `Base` class. – Enigmativity Mar 06 '19 at 02:11
-1

In your base (or encapsulated class if you choose to go that way):

    protected ReadOnlyCollection<T> GetSnapshot<T>() {
        return Array.AsReadOnly(ArrayReferences[typeof(T)].Select(s => (T)s).ToArray());
    }

Then you'd also add any other methods to view the data, e.g. to get a count:

    protected int GetCount<T>() {
        return ArrayReferences[typeof(T)].Count;
    }
monty
  • 1,543
  • 14
  • 30