1

There's a scenario I have where, in the main entry point of my C# program, I iterate a directory of managed DLLs, load each of them, and pass a factory object to each of them to get a mapping of implementations for various public interfaces. After the iteration is completed, I have one big map of interfaces and their corresponding implementations.

This factory object is needed by many unrelated objects in the system. There's also state that goes with the factory that identifies if specific DLLs were found, because knowing if I have a certain implementation sometimes influences the UI (disabling elements in the UI when they are not present).

What is the best way to share this factory + the accompanying informational state? I can either store it as a global, or I can pass it through hundreds of functions in my code, but I like neither of these solutions.

Challenges like this make it really hard to avoid the Singleton pattern. I can't be creating these factories on-demand all over the place because I would be repeating the same, heavy work each time and that's unnecessary.

What are some alternative solutions that don't involve abusing global variables (which is error prone), polluting interfaces, or impacting performance? Note that if the singleton pattern (as a class) is justified here and there are no reasonable alternatives, I can accept that as an answer.

As far as research goes, I did find answers like this one that mention dependency injection, however I'm not sure how that would apply here. I've also considered some sort of observer pattern for this, but again I don't see how I could use that mechanism to avoid the problems here. It feels like no matter what, when you boil each idea down to the essentials, it comes down to either global state or polluting interfaces.

5
  • 3
    All programming techniques have tradeoffs. If the advantages of a Singleton outweigh the disadvantages, use the Singleton. If you're careful with it, it won't matter. Commented Mar 18, 2020 at 20:23
  • 2
    Just curious on the wording "pass a factory object to each of them", do you mean passing a factory object to some holder class that serves as the "loaded" DLL? I could be misinterpreting that bit. For the other unrelated objects, shouldn't the instance returned by the factory denote the informational state? For example, someDLLFactory instance returns as some object DLLState<T> where T represents a specific implementation of the loaded DLLs. Commented Mar 18, 2020 at 20:40
  • @eparham7861 Specifically the factory here is a UnityContainer. I only have one of those and I run UnityContainer.Resolve<ISomething>() to get an instance of whatever implementation was mapped to interface ISomething in the DLL. The goal is that in the code, anything that needs dynamically loaded functionality invoke this factory (the instance of UnityContainer) to get it. Commented Mar 18, 2020 at 20:46
  • 2
    @void.pointer you pass the container into objects and call resolve? that's definitely a_wrong_thing(tm)
    – Ewan
    Commented Mar 18, 2020 at 22:29
  • 2
    Global Variables are a lot less problematic when they are read-only after creation.
    – GHP
    Commented Mar 19, 2020 at 13:24

3 Answers 3

5

Global variables are fine. They do make the dataflows less transparent, but for dataflows between distant components they can be a good tradeoff. Since you are writing an application and not a library, there's only a single “client” or context for those variables and keeping some global state is fine. Even for a library, global state representing the outside system configuration may be appropriate.

You can take steps to limit the downsides of using such global variables, namely keeping them as non-variable as possible. Consider offering access to the data only through an API that guarantees:

  1. the data can never be accessed in an uninitialized state (e.g. normal singleton initialization patterns ensure this)
  2. the data cannot be mutated by anyone else (e.g. provide methods for extracting essential information, or provide immutable views on the data structures)

Most dependency injection containers are not an alternative to global variables, they are an implicit global context. These DI containers merely offer a convenient interface for managing global configuration state.

3

Generally, global variables are only really "bad", when you have too many of them and they are publicly mutable.

In this case, it sounds like you have a single service class needed by the whole application. Any dependency injection framework would definitely be overkill in my opinion, as they are built to handle systems that have hundreds of interconnected service dependencies.

Typically, service-style classes (like the one you describe) end up being public static on the Program class (console application) or App class (WPF/Winforms application).

The key is that the members of the service class are not static; instead, it should be a normal class that can be created and initialized, and then the instance of the service class is stored in a public static field/property so that the rest of the application can access it.

A good question to ask yourself whenever you're wondering if a global is appropriate would be: how hard would it be to modify this application to use two different implementations of this service?

If you have:

public static Program
{
    public static readonly SomeService SomeService { get; private set; }

    public static void Main()
    {
        SomeService = new SomeService();
        SomeService.Init();
    }
}

and all your code uses Program.SomeService, it would be relatively clean and easy to change it to:

public static Program
{
    public static readonly SomeService SomeService { get; private set; }

    public static readonly SomeService AnotherSomeService { get; private set; }

    public static void Main()
    {
        SomeService = new SomeService();
        SomeService.Init();

        AnotherSomeService = new SomeService();
        AnotherSomeService.Init();
    }
}

and change whatever code needs this other instance to use Program.AnotherSomeService.

One thing to keep in mind when you have a service class that permeates your entire application like this though, is that you will almost certainly need to make any mutation of internal service state thread-safe via locks or some other synchronization strategy.

1
  • +1 for your point on when globals become bad. They're generally okay when they're not exposing mutable state, and when you limit their usage to when they're necessary or when the downside of the tradeoffs are too great. It sounds like the OP is using dependency injection, it's just one that they've implemented themselves. A lightweight DI library - if one exists in .NET/WF - may be a good fit for this use case.
    – Jason
    Commented Dec 28, 2021 at 15:42
3

Kind of guessing at what you mean here, I read it like this:

option 1: Global Variable

class UI
{
    Display()
    {
         Global.BigMap.Child.Child.Value < 10 ? displayComponent : dont
    }
}

option 2: Polluting interface

class UI
{
    private injectedBigMap
    Display()
    {
         injectedBigMap.ShouldDisplayComponent() ? displayComponent : dont
    }
}

I would go for a mix

class UIOptions : IUIOptions
{
     private injectedBigMap;
     ShouldDisplayComponent()
     {
         return injectedBigMap.Child.Child.Value < 10;
     }
}

class UI
{
    private injectedIUIOptions
    Display()
    {
         injectedIUIOptions.ShouldDisplayComponent() ? displayComponent : dont
    }
}

Now I can make a many IOptions for each class that needs one, separating the BigMap data and the logic of what it means from the component. But I keep a single BigMap and just pass the reference around, a 'global' variable but with scoped reference

If the BigMap changes I just need to adjust the Option classes. I can also mock them for testing.

EDIT

Just read your unity comment about passing the container around? This is a known anti pattern. But I'm confused as to why you would need to do it. Unity has built in support for dynamically loading classes and registering them via XML config.

Plus the whole point of having a container is that it resolves the dependencies for you automatically.

so in this case you can have

public class UI
{
    public UI(IUIOptions options)
    {
        this.options = options
    }
}

Main()
{
    container.Register<IUIOptions, UIOptions>()
    container.Register<UI>()

    var UI = container.Resolve<UI>(); //UIOptions automatically passed in!
}

Not...

public class UI
{
    public UI(IUnityContainer container)
    {
        this.options = container.Resolve<IUIOptions>(); //bad!!
    }
}

this way the calling class has no way to know what the dependencies are

Also, as I say Unity has the now rarely used xml config option, you can specify all your registrations dynamically in your app config

<alias alias="UIOptions" type="Plugin.UIOptions, Plugin.UIOptions" />
<alias alias="UI" type="Plugin.UI, Plugin.UI" />
<container>
   <register type="IUIOptions" mapTo="UIOptions"/>
   <register type="UI" mapTo="UI"/>
</container>
3
  • Another approach is to use a thread-static reference; most threads may have it set to the same singleton, but client code could use multiple instances if needed for testing or other scenarios.
    – supercat
    Commented Mar 19, 2020 at 0:07
  • One semantic that makes this challenging is when you need to use the UnityContainer to construct objects post-initialization. There's a way to do this with delayed composition, but I'm not sure if that's the ideal way. Basically the idea of allowing the unity container to be used later on by other objects is so those objects can be constructed later when the timing is right or we actually have prerequisites to construct them. Commented Mar 19, 2020 at 16:33
  • 1
    you can use the unity container later no prob, the idea is not to let other objects use it
    – Ewan
    Commented Mar 19, 2020 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.