0

Is there an elegant (or any) way to achieve following in C#?

  • Let's have a class ItemBase (further derivable to Item1, Item2...), which does not allow direct instantiation (non-public construction) - to prevent user to create any 'untracked' instance of Item*.
  • Let's have a non-static class Manager, whose instances (multiple ones allowed) only can create and provide instances of Item* (because they keep track of produced instances and do some additional work).
  • Let's have an optional requirement: The Manager instances would like to manipulate non-public members of the managed Item instances (similar like the Manager would be a friend of Item*).
  • It would be nice if the Manager is not forced to be derivation of Item*.
  • It would be nice if there is as little reflection as possible.

Notes:

  • If possible, please consider this as a question raising from process of thinking how to implement particular problem solution in a best and elegant way. I would like it to be general and no, I don't have sources and yes, I have already tried some variants, but none of them satisfied my needs. Thank you.

  • As far as I know, there is no acceptable friend alternative (any of internal and InternalsVisibleToAttribute seems to be good), so the ItemBase just provides the 'special' (but public) modification methods and the user must be aware, these methods are not for him :o(

  • I like this solution, but I'm not able to invent, how to allow multiple Manager instances using it.

Community
  • 1
  • 1
sharpener
  • 1,383
  • 11
  • 22
  • What's wrong with using `internal`? Are users going to be using your source code directly and not leveraging your library as a referenced DLL? – Chris Sinclair Sep 11 '15 at 14:12
  • yes, let users += another programmer using the code – sharpener Sep 11 '15 at 14:21
  • Consider defining multiple interfaces, an `IItemBase`, `IItem1`, `IItem2` which house the "public" API that's meant to be consumed and an `IInternalItem` which houses the internal/private manipulation methods. Your factories/API/usages should normally be bound against the `IItem1` interface; that is, users don't deal with `Item1` _class_. Your Manager classes can deal with the `Item1` _class_ (or the `IInternalItem` interface) and use the private manipulation methods. – Chris Sinclair Sep 11 '15 at 14:41
  • This way your users/programmer using the code won't normally mess up; they'd have to _purposely_ cast to `Item1` _class_ or `IInternalItem` to access the hidden methods. EDIT: I forgot to mention, you could consider using [explicit interface implementation](https://msdn.microsoft.com/en-us/library/ms173157.aspx) for this as well. – Chris Sinclair Sep 11 '15 at 14:41

2 Answers2

0

I think this might answer your problem :

public class ItemBase

{
    protected ItemBase()
    {

    }
    public void PublicMethod() { }
    public int PublicProperty { get; set; }
}

public class Factory
{
    private class PrivateItemBase : ItemBase
    {
        public void PrivateMethod() { }
        public int PrivateProperty { get; set; }
    }

    public Factory(int id)
    {

    }

    public IEnumerable<ItemBase> Items { get; private set; }
    public ItemBase CreateItem()
    {
        PrivateItemBase rValue = new PrivateItemBase();

        rValue.PrivateMethod();
        rValue.PrivateProperty = 4;

        return rValue;
    }
}
xum59
  • 841
  • 9
  • 16
  • Consider the ItemBase as abstract class. It might get derived by another classes, like `public class Item1 : ItemBase {...};` and even more: `public class Item1Better : Item1 {...};`. The factory gets info about the type to be created (eg. via the enum value or Type class). – sharpener Sep 11 '15 at 15:10
0

Ok, giving up. If this might help to fully understand the purpose, there is the less bad solution I've (currently) ended up. Passing the creation functions is done via static constructors (which are not accessible by the users), unfortunately the ugly thing is their invocation...

Any idea how to make it better?

The item definitions:

namespace SpecialFactory
{
    public enum ItemType
    {
        Item1,
        Item2,
        // ... Anyone deriving the Item* should add an item here
    }

    public abstract class ItemBase
    {
        public abstract ItemType Id {get;}

        public static void RegisterAllCreators()
        {
            // Force static constructors invocation
            var it = Item1.ClassId | Item2.ClassId; // Anyone deriving the Item* should ensure invocation of Manager.RegisterCreator
        }
    }

    public class Item1 : ItemBase
    {
        static Item1()
        {
            Manager.RegisterCreator(ItemType.Item1, () => new Item1());
        }

        protected Item1()
        {
        }

        public static   ItemType ClassId => ItemType.Item1;
        public override ItemType Id      => ClassId;
    }

    public class Item2 : ItemBase
    {
        static Item2()
        {
            Manager.RegisterCreator(ItemType.Item2, () => new Item2());
        }

        protected Item2()
        {
        }

        public static   ItemType ClassId => ItemType.Item2;
        public override ItemType Id      => ClassId;
    }
}

The manager:

namespace SpecialFactory
{
    public class Manager
    {
        static Manager()
        {
            ItemBase.RegisterAllCreators();
        }

        protected static Dictionary<ItemType, Func<ItemBase>> creators = new Dictionary<ItemType, Func<ItemBase>>();
        protected readonly List<ItemBase> managedItems = new List<ItemBase>();

        protected ItemBase CreateItem(ItemType type)
        {
            ItemBase item = null;

            if (creators.ContainsKey(type))
            {
                if ((item = creators[type]()) != null)
                    managedItems.Add(item);
            }

            return item;    
        }

        public static void RegisterCreator(ItemType type, Func<ItemBase> creator)
        {
            if (!creators.ContainsKey(type))
                creators[type] = creator;
        }

        public Manager()
        {

        }

        public ItemBase Test(ItemType type)
        {
            // var notAllowed = new Item1();
            var allowed = CreateItem(type);

            return allowed;
        }
    }
}

The test:

namespace SpecialFactory
{
    class Program
    {
        static void Main(string[] args)
        {
            var m1 = new Manager();
            var m2 = new Manager();

            var i1 = m1.Test(ItemType.Item1);
            var i2 = m2.Test(ItemType.Item2);
        }
    }
}
sharpener
  • 1,383
  • 11
  • 22