Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject Memoize Instances in Singleton Scope

I'm using Ninject to instantiate some objects with a constructor arg passed, e.g.:

class MyClass
{
    public MyClass(string myArg)
    {
        this.myArg = myArg;
    }
}

The number of instances I need of this class won't be known until runtime, but what I want to do is ensure that each variation of myArg results in a different singleton instance (so asking for the same value twice returns the same instance, but different args return different instances).

Does anyone know of a good, preferably built-in, way of doing this?

I found an article written for an older version of Ninject How To Ensure One Instance per Variation of Activation Parameters but was hoping there'd be a tidier solution for the newer version.

Edit

Here's what I went with, adapted from Akim's answer below:

private readonly ConcurrentBag<string> scopeParameters = new ConcurrentBag<string>();

internal object ParameterScope(IContext context, string parameterName)
{
    var param = context.Parameters.First(p => p.Name.Equals(parameterName));
    var paramValue = param.GetValue(context, context.Request.Target) as string;
    paramValue = string.Intern(paramValue);

    if (paramValue != null && !scopeParameters.Contains(paramValue))
    {
        scopeParameters.Add(paramValue);
    }

    return paramValue;
}

public override void Load()
{
    Bind<MyClass>()
            .ToSelf()
            .InScope(c => ParameterScope(c, "myArg"));

    Bind<IMyClassFactory>()
        .ToFactory();
}
like image 1000
Adam Rodger Avatar asked Oct 22 '22 19:10

Adam Rodger


1 Answers

You could achieve require behaviour by providing custom scope using IBindingNamedWithOrOnSyntax<T> InScope(Func<IContext, object> scope) method for MyClass binding

Indicates that instances activated via the binding should be re-used as long as the object returned by the provided callback remains alive (that is, has not been garbage collected).

So, you need to return value of first constructor argument from Func<IContext, object> scopeand make sure that garbage-collector would not collect it.

Here is a snippet:

public class Module : NinjectModule
{
    // stores string myArg to protect from CG
    ConcurrentBag<string> ParamSet = new ConcurrentBag<string>();

    public override void Load()
    {
        Bind<MyClass>()
            .ToSelf()
            // custom scope
            .InScope((context) =>
                {
                    // get first constructor argument
                    var param = context.Parameters.First().GetValue(context, context.Request.Target) as string;                    

                    // retrieves system reference to string
                    param = string.Intern(param);

                    // protect value from CG
                    if(param != null && ParamSet.Contains(param))
                    {
                        // protect from GC
                        ParamSet.Add(param);
                    }

                    // make Ninject to return same instance for this argument
                    return param;
                });
    }
}

ps: full sample code with unittests

like image 165
Akim Avatar answered Oct 24 '22 11:10

Akim