Search code examples
c#inheritancestatic-methodsmembernon-static

Accessing inherited members from static methods - Singleton Inheritance?


Summary of the problem

Actually I just want to use normal inheritance functionality.
The catch is, that some of my methods are static, so they cannot access the inherited properties like normal. Furthermore, I cannot pass anything to the static methods (like the instance of the object), because they are called by "external triggers".
On the other side I don't need multiple instances, meaning something like a singleton would be fine.

Let me explain you my situation step by step:
(Or just jump to the first code sample to see the MCV example)

Requirement 1 - Static methods accessing members

I'm using the library Harmony for patching methods of an existing Assembly. The patching methods must be static and are called by the "hooks" of the library.
I add new player upgrades to a game. For each upgrade I want to patch I create a class with members, like name, description, image ... One Upgrade must be only patched once, so the solution here would be making everthing static.
But ...

Requirement 2 - Inheritance

Because these upgrades share a lot of common members and methods, I make a BaseUpgrade class and derive from it.
Each specific implementation can now assign their values to the common fields like name, description ... and inherit the rest (the methods using the members).
Yet the members aren't accessible from the static patching methods anymore. For that I can use Singletons.
I want to inherit all the Singleton stuff too, making it generic. This way only the base class has all the Singeton code.
One approach would be this solution: https://stackoverflow.com/a/16865465
However ...

Requirement 3 - Having a Collection of the base class

I need to have collections of the base class and use them in dictionaries (both as key and value). But that doesn't seem to work with generic classes.
I found Collection of generic types, but I'm stuck. I don't know if incorporating this will actually work. At least it would complicate things even more.

Would that work? Is there maybe a much more simple approach to my problem?


MCV Example of the Basic Scenario

using System;
using System.Collections.Generic;

namespace Using_Singleton
{

    // This is the version trying to incorperate the inheritable singleton
    class Base<T> where T : Base<T>, new()
    {
        #region Singleton stuff

        private static T _instance;

        public static T Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new T();
                return _instance;
            }
            set => _instance = value;
        }

        #endregion

        public string name;     // Should be accessible by the derived class' static methods
        public string desc;

        protected Base()
        {
            name = "Base";
        }

        public void printName()
        {
            Console.WriteLine(name);
        }
    }

    class FirstChild : Base<FirstChild>
    {
        public int number;      // Should be accessible by the class' static methods

        public FirstChild()
        {
            name = "The first child";
            number = 7;
        }

        public static void StaticMethod_FirstChild()
        {
            Console.WriteLine("StaticMethod_FirstChild: I can access all member variables! :-)");
            Console.WriteLine("Name: " + Instance.name + ", Number: " + Instance.number);     // This is now working
        }

    }

    class SecondChild : Base<SecondChild>
    {
        public float myfloat;

        public SecondChild()
        {
            name = "The second child";
            myfloat = 0.3f;
        }

        public static void StaticMethod_SecondChild()
        {
            Console.WriteLine("StaticMethod_SecondChild: I can access all member variables! :-)");
            Console.WriteLine("Name 2x: " + Instance.name + " " + Instance.name);       // This is now working
        }
    }

    class Manager       // Manages instances/singletons which derive from "Base" by using a collection of the Base class
    {
        //Dictionary<string, Base> itemDict;        // ******* This is now broken

        public Manager()
        {
            //itemDict = new Dictionary<string, Base>();

            //Base addItem;

            //addItem = new FirstChild();
            //itemDict.Add(addItem.GetType().Name, addItem);

            //addItem = new SecondChild();
            //itemDict.Add(addItem.GetType().Name, addItem);

            // Simulating the external call of one static method
            SecondChild.StaticMethod_SecondChild();
            Console.WriteLine();
        }

        public void DoSomething()
        {
            //foreach (var item in itemDict)
            //{
            //  item.Value.printName();
            //}
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Manager manager = new Manager();
            manager.DoSomething();

            Console.ReadLine();
        }
    }

}

Example using Inheritable Singletons

using System;
using System.Collections.Generic;

namespace Using_Singleton
{

    // This is the version trying to incorperate the inheritable singleton
    class Base<T> where T : Base<T>, new()
    {
        #region Singleton stuff

        private static T _instance;

        public static T Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new T();
                return _instance;
            }
            set => _instance = value;
        }

        #endregion

        public string name;     // Should be accessible by the derived class' static methods
        public string desc;

        protected Base()
        {
            name = "Base";
        }

        public void printName()
        {
            Console.WriteLine(name);
        }
    }

    class FirstChild : Base<FirstChild>
    {
        public int number;      // Should be accessible by the class' static methods

        public FirstChild()
        {
            name = "The first child";
            number = 7;
        }

        public static void StaticMethod_FirstChild()
        {
            Console.WriteLine("StaticMethod_FirstChild: I can access all member variables! :-)");
            Console.WriteLine("Name: " + Instance.name + ", Number: " + Instance.number);     // This is now working
        }

    }

    class SecondChild : Base<SecondChild>
    {
        public float myfloat;

        public SecondChild()
        {
            name = "The second child";
            myfloat = 0.3f;
        }

        public static void StaticMethod_SecondChild()
        {
            Console.WriteLine("StaticMethod_SecondChild: I can access all member variables! :-)");
            Console.WriteLine("Name 2x: " + Instance.name + " " + Instance.name);       // This is now working
        }
    }

    class Manager       // Manages instances/singletons which derive from "Base" by using a collection of the Base class
    {
        //Dictionary<string, Base> itemDict;        // ******* This is now broken

        public Manager()
        {
            //itemDict = new Dictionary<string, Base>();

            //Base addItem;

            //addItem = new FirstChild();
            //itemDict.Add(addItem.GetType().Name, addItem);

            //addItem = new SecondChild();
            //itemDict.Add(addItem.GetType().Name, addItem);

            // Simulating the external call of one static method
            SecondChild.StaticMethod_SecondChild();
            Console.WriteLine();
        }

        public void DoSomething()
        {
            //foreach (var item in itemDict)
            //{
            //  item.Value.printName();
            //}
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Manager manager = new Manager();
            manager.DoSomething();

            Console.ReadLine();
        }
    }
}

Solution

  • After adding Inheritable Singletons to the small example I tried adding support for a generic collection, as suggested by Jon Saunders.

    Basically making either an interface or class "above" the singleton class (the singleton class inherits it) and using this for the collection or in method parameters.

    In this new non-generic class I've put all fields and methods. The constructors of the top non-generic class and the generic class are protected, so they cannot be instantiated.

    Working solution

    using System;
    using System.Collections.Generic;
    
    namespace Using_Singleton_And_GenericCollection
    {
        // This is the version trying to incorperate the inheritable singleton and a generic collection
    
        abstract class NonGenericBase       // Adding this (class or interface) make the use of collections possible.
        {
            public string name;
            public string desc;
    
            public void printName()
            {
                Console.WriteLine("\t" + name);
            }
    
            protected NonGenericBase() { }
        }
    
        class Base<T> : NonGenericBase where T : Base<T>, new()
        {
            #region Singleton stuff
    
            protected static T _instance;
    
            public static T Instance
            {
                get
                {
                    if (_instance == null)
                        _instance = new T();
                    return _instance;
                }
                set => _instance = value;
            }
    
            #endregion
    
            //public string name;     // Moved to parent
            //public string desc;
    
            protected Base()
            {
                name = "Base";
            }
        }
    
    
        class FirstChild : Base<FirstChild>
        {
            public int number;      // Should be accessible by the class' static methods
    
            public FirstChild()
            {
                name = "The first child";
                number = 7;
            }
    
            public static void StaticMethod_FirstChild()
            {
                Console.WriteLine("\tStaticMethod_FirstChild: I can access all member variables! :-)");
                Console.WriteLine("\tName: " + Instance.name + ", Number: " + Instance.number);     // This is now working
            }
        }
    
        class SecondChild : Base<SecondChild>
        {
            public float myfloat;
    
            public SecondChild()
            {
                name = "The second child";
                myfloat = 0.3f;
            }
    
            public static void StaticMethod_SecondChild()
            {
                Console.WriteLine("\tStaticMethod_SecondChild: I can access all member variables! :-)");
                Console.WriteLine("\tName 2x: " + Instance.name + ", " + Instance.name);       // This is now working
            }
        }
    
    
        class Manager       // Manages instances/singletons which derive from "Base" by using a collection of the Base class
        {
            public Dictionary<string, NonGenericBase> itemDict;
    
            public Manager()
            {
                itemDict = new Dictionary<string, NonGenericBase>();
    
                NonGenericBase addItem;
    
                addItem = FirstChild.Instance;
                itemDict.Add(addItem.GetType().Name, addItem);
    
                addItem = SecondChild.Instance;
                itemDict.Add(addItem.GetType().Name, addItem);
            }
    
            public void DoSomething()
            {
                foreach (var item in itemDict)
                {
                    item.Value.printName();
                }
                Console.WriteLine();
            }
        }
    
    
        class Program
        {
            static void Main(string[] args)
            {
                var sec = new SecondChild();
    
                Console.WriteLine("Access Singletons");
                Manager manager = new Manager();
                manager.DoSomething();
    
                Console.WriteLine("Change Singletons");
                manager.itemDict[nameof(FirstChild)].name = "first name changed";
                SecondChild.Instance.name = "second name changed too";
                manager.DoSomething();
    
                Console.WriteLine("Create and change a non-Singleton instance of FirstChild");
                var initItem = new FirstChild();
                initItem.printName();
                initItem.name = "Non-Singleton name changed";
                initItem.printName();
                Console.WriteLine();
    
                Console.WriteLine("Access Singletons");
                manager.DoSomething();
    
                Console.WriteLine("Call static method of FirstChild");
                FirstChild.StaticMethod_FirstChild();         //Simulating the external call of one static method
                Console.WriteLine();
    
                Console.ReadKey();
            }
        }
    } 
    

    Output

    Access Singletons
            The first child
            The second child
    
    Change Singletons
            first name changed
            second name changed too
    
    Create and change a non-Singleton instance of FirstChild
            The first child
            Non-Singleton name changed
    
    Access Singletons
            first name changed
            second name changed too
    
    Call static method of FirstChild
            StaticMethod_FirstChild: I can access all member variables! :-)
            Name: first name changed, Number: 7
    

    Caveat

    Because of the "new()" here

    class Base<T> : NonGenericBase where T : Base<T>, new()
    

    the constructors of the specific sub classes need to be public. This means that the Singletons aren't enforced, but "just" an option (see the output for an example).

    BTownTKD states that in his answer he states that fact and links to his attempt to solve this via reflection an manually invoking private constructors here: Singleton.cs

    Conclusion

    Thanks to doing the MCV example for this question and trying it again with a greatly reduced complexity I've found the solution myself.
    So this questioning process here and improving my initial post helped me with it. :-)