diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b3ef7f59e..50bb87b9c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -40,6 +40,7 @@ END TEMPLATE--> ### New features * The server will now check for any unknown CVars at startup, to possibly locate typos in your config file. +* `IDependencyCollection` is now thread safe. ### Bugfixes diff --git a/Robust.Shared/IoC/DependencyCollection.cs b/Robust.Shared/IoC/DependencyCollection.cs index f02e6e96b..d951655fd 100644 --- a/Robust.Shared/IoC/DependencyCollection.cs +++ b/Robust.Shared/IoC/DependencyCollection.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Threading; using JetBrains.Annotations; using Robust.Shared.IoC.Exceptions; using Robust.Shared.Utility; @@ -19,12 +21,23 @@ namespace Robust.Shared.IoC internal sealed class DependencyCollection : IDependencyCollection { private delegate void InjectorDelegate(object target, object[] services); - private static readonly Type[] InjectorParameters = {typeof(object), typeof(object[])}; + + private delegate T DependencyFactoryDelegateInternal( + IReadOnlyDictionary services) + where T : class; + + private static readonly Type[] InjectorParameters = { typeof(object), typeof(object[]) }; /// - /// Dictionary that maps the types passed to to their implementation. + /// Dictionary that maps the types passed to to their implementation. + /// This is the first dictionary to get hit on a resolve. /// - private readonly Dictionary _services = new(); + /// + /// Immutable and atomically swapped to provide thread safety guarantees. + /// + private ImmutableDictionary _services = ImmutableDictionary.Empty; + + // Start fields used for building new services. /// /// The types interface types mapping to their registered implementations. @@ -32,19 +45,25 @@ namespace Robust.Shared.IoC /// private readonly Dictionary _resolveTypes = new(); - private readonly Dictionary> _resolveFactories = new(); - + private readonly Dictionary> _resolveFactories = new(); private readonly Queue _pendingResolves = new(); - private readonly Queue _newInstances = new(); + + private readonly object _serviceBuildLock = new(); + + // End fields for building new services. // To do injection of common types like components, we make DynamicMethods to do the actual injecting. // This is way faster than reflection and should be allocation free outside setup. - private readonly Dictionary _injectorCache = + private readonly Dictionary _injectorCache = new(); + private readonly ReaderWriterLockSlim _injectorCacheLock = new(); + private readonly IDependencyCollection? _parentCollection; - public DependencyCollection() { } + public DependencyCollection() + { + } public DependencyCollection(IDependencyCollection parentCollection) { @@ -83,7 +102,15 @@ namespace Robust.Shared.IoC /// public bool TryResolveType(Type objectType, [MaybeNullWhen(false)] out object instance) { - if(!_services.TryGetValue(objectType, out instance)) + return TryResolveType(objectType, _services, out instance); + } + + private bool TryResolveType( + Type objectType, + IReadOnlyDictionary services, + [MaybeNullWhen(false)] out object instance) + { + if (!services.TryGetValue(objectType, out instance)) return _parentCollection is not null && _parentCollection.TryResolveType(objectType, out instance); return true; @@ -94,13 +121,15 @@ namespace Robust.Shared.IoC where TImplementation : class, TInterface where TInterface : class { - Register(() => + Register(services => { - var objectType = typeof(TImplementation); - var constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var objectType = typeof(TImplementation); + var constructors = + objectType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (constructors.Length != 1) - throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' requires exactly one constructor."); + throw new InvalidOperationException( + $"Dependency '{typeof(TImplementation).FullName}' requires exactly one constructor."); var chosenConstructor = constructors[0]; var constructorParams = constructors[0].GetParameters(); @@ -110,7 +139,7 @@ namespace Robust.Shared.IoC { var param = constructorParams[index]; - if (TryResolveType(param.ParameterType, out var instance)) + if (TryResolveType(param.ParameterType, services, out var instance)) { parameters[index] = instance; } @@ -118,45 +147,73 @@ namespace Robust.Shared.IoC { if (_resolveTypes.ContainsKey(param.ParameterType)) { - throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' ctor requires {param.ParameterType.FullName} registered before it."); + throw new InvalidOperationException( + $"Dependency '{typeof(TImplementation).FullName}' ctor requires {param.ParameterType.FullName} registered before it."); } - throw new InvalidOperationException($"Dependency '{typeof(TImplementation).FullName}' ctor has unknown dependency {param.ParameterType.FullName}"); + throw new InvalidOperationException( + $"Dependency '{typeof(TImplementation).FullName}' ctor has unknown dependency {param.ParameterType.FullName}"); } } - return (TImplementation) chosenConstructor.Invoke(parameters); + return (TImplementation)chosenConstructor.Invoke(parameters); }, overwrite); } /// - public void Register(DependencyFactoryDelegate factory, bool overwrite = false) + public void Register( + DependencyFactoryDelegate factory, + bool overwrite = false) where TImplementation : class, TInterface where TInterface : class - { - var interfaceType = typeof(TInterface); - CheckRegisterInterface(interfaceType, typeof(TImplementation), overwrite); + Register(typeof(TInterface), typeof(TImplementation), factory, overwrite); + } - _resolveTypes[interfaceType] = typeof(TImplementation); - _resolveFactories[typeof(TImplementation)] = factory; - _pendingResolves.Enqueue(interfaceType); + private void Register( + DependencyFactoryDelegateInternal factory, + bool overwrite = false) + where TImplementation : class, TInterface + where TInterface : class + { + Register(typeof(TInterface), typeof(TImplementation), factory, overwrite); } /// - public void Register(Type implementation, DependencyFactoryDelegate? factory = null, - bool overwrite = false) => Register(implementation, implementation, factory, overwrite); + public void Register( + Type implementation, + DependencyFactoryDelegate? factory = null, + bool overwrite = false) + { + Register(implementation, implementation, factory, overwrite); + } - public void Register(Type interfaceType, Type implementation, DependencyFactoryDelegate? factory = null, bool overwrite = false) + public void Register( + Type interfaceType, + Type implementation, + DependencyFactoryDelegate? factory = null, + bool overwrite = false) + { + Register(interfaceType, implementation, FactoryToInternal(factory), overwrite); + } + + private void Register( + Type interfaceType, + Type implementation, + DependencyFactoryDelegateInternal? factory = null, + bool overwrite = false) { CheckRegisterInterface(interfaceType, implementation, overwrite); - object DefaultFactory() + object DefaultFactory(IReadOnlyDictionary services) { - var constructors = implementation.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var constructors = + implementation.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance); if (constructors.Length != 1) - throw new InvalidOperationException($"Dependency '{implementation.FullName}' requires exactly one constructor."); + throw new InvalidOperationException( + $"Dependency '{implementation.FullName}' requires exactly one constructor."); var chosenConstructor = constructors[0]; var constructorParams = chosenConstructor.GetParameters(); @@ -166,7 +223,7 @@ namespace Robust.Shared.IoC { var param = constructorParams[index]; - if (TryResolveType(param.ParameterType, out var instance)) + if (TryResolveType(param.ParameterType, services, out var instance)) { parameters[index] = instance; } @@ -174,53 +231,60 @@ namespace Robust.Shared.IoC { if (_resolveTypes.ContainsKey(param.ParameterType)) { - throw new InvalidOperationException($"Dependency '{implementation.FullName}' ctor requires {param.ParameterType.FullName} registered before it."); + throw new InvalidOperationException( + $"Dependency '{implementation.FullName}' ctor requires {param.ParameterType.FullName} registered before it."); } - throw new InvalidOperationException($"Dependency '{implementation.FullName}' ctor has unknown dependency {param.ParameterType.FullName}"); + throw new InvalidOperationException( + $"Dependency '{implementation.FullName}' ctor has unknown dependency {param.ParameterType.FullName}"); } } return chosenConstructor.Invoke(parameters); } - _resolveTypes[interfaceType] = implementation; - _resolveFactories[implementation] = factory ?? DefaultFactory; - _pendingResolves.Enqueue(interfaceType); + lock (_serviceBuildLock) + { + _resolveTypes[interfaceType] = implementation; + _resolveFactories[implementation] = factory ?? DefaultFactory; + _pendingResolves.Enqueue(interfaceType); + } } [AssertionMethod] private void CheckRegisterInterface(Type interfaceType, Type implementationType, bool overwrite) { - if (!_resolveTypes.ContainsKey(interfaceType)) - return; - - if (!overwrite) + lock (_serviceBuildLock) { - throw new InvalidOperationException - ( - string.Format( - "Attempted to register already registered interface {0}. New implementation: {1}, Old implementation: {2}", - interfaceType, implementationType, _resolveTypes[interfaceType] - )); - } + if (!_resolveTypes.ContainsKey(interfaceType)) + return; - if (_services.ContainsKey(interfaceType)) - { - throw new InvalidOperationException( - $"Attempted to overwrite already instantiated interface {interfaceType}."); + if (!overwrite) + { + throw new InvalidOperationException + ( + string.Format( + "Attempted to register already registered interface {0}. New implementation: {1}, Old implementation: {2}", + interfaceType, implementationType, _resolveTypes[interfaceType] + )); + } + + if (_services.ContainsKey(interfaceType)) + { + throw new InvalidOperationException( + $"Attempted to overwrite already instantiated interface {interfaceType}."); + } } } - /// - public void RegisterInstance(object implementation, bool overwrite = false, bool deferInject = false) + public void RegisterInstance(object implementation, bool overwrite = false) where TInterface : class { - RegisterInstance(typeof(TInterface), implementation, overwrite, deferInject); + RegisterInstance(typeof(TInterface), implementation, overwrite); } /// - public void RegisterInstance(Type type, object implementation, bool overwrite = false, bool deferInject = false) + public void RegisterInstance(Type type, object implementation, bool overwrite = false) { if (implementation == null) throw new ArgumentNullException(nameof(implementation)); @@ -229,26 +293,7 @@ namespace Robust.Shared.IoC throw new InvalidOperationException( $"Implementation type {implementation.GetType()} is not assignable to type {type}"); - CheckRegisterInterface(type, implementation.GetType(), overwrite); - - if(!deferInject) - { - // do the equivalent of BuildGraph with a single type. - _resolveTypes[type] = implementation.GetType(); - _services[type] = implementation; - - InjectDependencies(implementation, true); - - if (implementation is IPostInjectInit init) - init.PostInject(); - } - else - { - _resolveTypes[type] = implementation.GetType(); - _services[type] = implementation; - - _newInstances.Enqueue(implementation); - } + Register(type, implementation.GetType(), () => implementation, overwrite); } /// @@ -259,19 +304,26 @@ namespace Robust.Shared.IoC service.Dispose(); } - _services.Clear(); - _resolveTypes.Clear(); - _resolveFactories.Clear(); - _pendingResolves.Clear(); - _newInstances.Clear(); - _injectorCache.Clear(); + _services = ImmutableDictionary.Empty; + + lock (_serviceBuildLock) + { + _resolveTypes.Clear(); + _resolveFactories.Clear(); + _pendingResolves.Clear(); + } + + using (_injectorCacheLock.WriteGuard()) + { + _injectorCache.Clear(); + } } /// [System.Diagnostics.Contracts.Pure] public T Resolve() { - return (T) ResolveType(typeof(T)); + return (T)ResolveType(typeof(T)); } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] @@ -289,14 +341,16 @@ namespace Robust.Shared.IoC } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - public void Resolve([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, [NotNull] ref T3? instance3) + public void Resolve([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, + [NotNull] ref T3? instance3) { Resolve(ref instance1, ref instance2); Resolve(ref instance3); } [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - public void Resolve([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, [NotNull] ref T3? instance3, [NotNull] ref T4? instance4) + public void Resolve([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, + [NotNull] ref T3? instance3, [NotNull] ref T4? instance4) { Resolve(ref instance1, ref instance2); Resolve(ref instance3, ref instance4); @@ -329,71 +383,81 @@ namespace Robust.Shared.IoC /// public void BuildGraph() { - // List of all objects we need to inject dependencies into. - var injectList = new List(); - - // First we build every type we have registered but isn't yet built. - // This allows us to run this after the content assembly has been loaded. - while(_pendingResolves.Count > 0) + lock (_serviceBuildLock) { - Type key = _pendingResolves.Dequeue(); - var value = _resolveTypes[key]; + // List of all objects we need to inject dependencies into. + var injectList = new List(); - // Find a potential dupe by checking other registered types that have already been instantiated that have the same instance type. - // Can't catch ourselves because we're not instantiated. - // Ones that aren't yet instantiated are about to be and will find us instead. - var (type, _) = _resolveTypes.FirstOrDefault(p => _services.ContainsKey(p.Key) && p.Value == value)!; + var newDeps = _services.ToBuilder(); - // Interface key can't be null so since KeyValuePair<> is a struct, - // this effectively checks whether we found something. - if (type != null) + // First we build every type we have registered but isn't yet built. + // This allows us to run this after the content assembly has been loaded. + while (_pendingResolves.Count > 0) { - // We have something with the same instance type, use that. - _services[key] = _services[type]; - continue; + Type key = _pendingResolves.Dequeue(); + var value = _resolveTypes[key]; + + // Find a potential dupe by checking other registered types that have already been instantiated that have the same instance type. + // Can't catch ourselves because we're not instantiated. + // Ones that aren't yet instantiated are about to be and will find us instead. + var (type, _) = + _resolveTypes.FirstOrDefault(p => newDeps.ContainsKey(p.Key) && p.Value == value)!; + + // Interface key can't be null so since KeyValuePair<> is a struct, + // this effectively checks whether we found something. + if (type != null) + { + // We have something with the same instance type, use that. + newDeps[key] = newDeps[type]; + continue; + } + + try + { + // Yay for delegate covariance + object instance = _resolveFactories[value].Invoke(newDeps); + newDeps[key] = instance; + injectList.Add(instance); + } + catch (TargetInvocationException e) + { + throw new ImplementationConstructorException(value, e.InnerException); + } } - try + // Because we only ever construct an instance once per registration, there is no need to keep the factory + // delegates. Also we need to free the delegates because lambdas capture variables. + _resolveFactories.Clear(); + + // Graph built, go over ones that need injection. + foreach (var implementation in injectList) { - // Yay for delegate covariance - object instance = _resolveFactories[value].Invoke(); - _services[key] = instance; - injectList.Add(instance); + InjectDependenciesReflection(implementation, newDeps); } - catch (TargetInvocationException e) + + // Atomically set the new dict of services. + _services = newDeps.ToImmutable(); + + foreach (var injectedItem in injectList.OfType()) { - throw new ImplementationConstructorException(value, e.InnerException); + injectedItem.PostInject(); } } - - // Because we only ever construct an instance once per registration, there is no need to keep the factory - // delegates. Also we need to free the delegates because lambdas capture variables. - _resolveFactories.Clear(); - - // add all the newly registered instances to the inject list - while (_newInstances.Count > 0) - { - injectList.Add(_newInstances.Dequeue()); - } - - // Graph built, go over ones that need injection. - foreach (var implementation in injectList) - { - InjectDependencies(implementation, true); - } - - foreach (var injectedItem in injectList.OfType()) - { - injectedItem.PostInject(); - } } /// - public void InjectDependencies(object obj, bool oneOff=false) + public void InjectDependencies(object obj, bool oneOff = false) { var type = obj.GetType(); - if (!_injectorCache.TryGetValue(type, out var injector)) + bool found; + CachedInjector injector; + using (_injectorCacheLock.ReadGuard()) + { + found = _injectorCache.TryGetValue(type, out injector); + } + + if (!found) { if (oneOff) { @@ -403,8 +467,7 @@ namespace Robust.Shared.IoC return; } - CacheInjector(type); - injector = _injectorCache[type]; + injector = CacheInjector(type); } var (@delegate, services) = injector; @@ -415,6 +478,11 @@ namespace Robust.Shared.IoC } private void InjectDependenciesReflection(object obj) + { + InjectDependenciesReflection(obj, _services); + } + + private void InjectDependenciesReflection(object obj, IReadOnlyDictionary services) { var type = obj.GetType(); foreach (var field in type.GetAllFields()) @@ -425,7 +493,7 @@ namespace Robust.Shared.IoC } // Not using Resolve() because we're literally building it right now. - if (TryResolveType(field.FieldType, out var dep)) + if (TryResolveType(field.FieldType, services, out var dep)) { // Quick note: this DOES work with read only fields, though it may be a CLR implementation detail. field.SetValue(obj, dep); @@ -444,8 +512,13 @@ namespace Robust.Shared.IoC } } - private void CacheInjector(Type type) + private CachedInjector CacheInjector(Type type) { + using var _ = _injectorCacheLock.WriteGuard(); + // Check in case value got filled in right before we acquired the lock. + if (_injectorCache.TryGetValue(type, out var cached)) + return cached; + var fields = new List(); foreach (var field in type.GetAllFields()) @@ -458,10 +531,11 @@ namespace Robust.Shared.IoC fields.Add(field); } + // No dependency fields, nothing to inject so no point setting this all up. if (fields.Count == 0) { - _injectorCache.Add(type, (null, null)); - return; + _injectorCache.Add(type, default); + return default; } var dynamicMethod = new DynamicMethod($"_injector<>{type}", null, InjectorParameters, type, true); @@ -509,7 +583,23 @@ namespace Robust.Shared.IoC generator.Emit(OpCodes.Ret); var @delegate = (InjectorDelegate)dynamicMethod.CreateDelegate(typeof(InjectorDelegate)); - _injectorCache.Add(type, (@delegate, services.ToArray())); + cached = new CachedInjector(@delegate, services.ToArray()); + _injectorCache.Add(type, cached); + + return cached; } + + [return: NotNullIfNotNull("factory")] + private static DependencyFactoryDelegateInternal? FactoryToInternal( + DependencyFactoryDelegate? factory) + where T : class + { + if (factory == null) + return null; + + return _ => factory(); + } + + private record struct CachedInjector(InjectorDelegate? Delegate, object[]? Services); } } diff --git a/Robust.Shared/IoC/IDependencyCollection.cs b/Robust.Shared/IoC/IDependencyCollection.cs index aeafcf9fb..31d859013 100644 --- a/Robust.Shared/IoC/IDependencyCollection.cs +++ b/Robust.Shared/IoC/IDependencyCollection.cs @@ -26,6 +26,9 @@ namespace Robust.Shared.IoC /// To use the IoCManager, it first needs some types registered through . /// These implementations can then be fetched with , or through field injection with . /// + /// + /// This type is thread safe: registration and can run concurrently with resolves. + /// /// /// public interface IDependencyCollection @@ -112,13 +115,7 @@ namespace Robust.Shared.IoC /// If true, do not throw an if an interface is already registered, /// replace the current implementation instead. /// - /// - /// Defer field injection until is called. - /// If this is false, dependencies will be immediately injected. If the registered type requires dependencies - /// that don't exist yet because you have not called BuildGraph, set this to true. - /// - void RegisterInstance(object implementation, bool overwrite = false, bool deferInject = false) - where TInterface : class; + void RegisterInstance(object implementation, bool overwrite = false) where TInterface : class; /// /// Registers an interface to an existing instance of an implementation, @@ -132,12 +129,7 @@ namespace Robust.Shared.IoC /// If true, do not throw an if an interface is already registered, /// replace the current implementation instead. /// - /// - /// Defer field injection until is called. - /// If this is false, dependencies will be immediately injected. If the registered type requires dependencies - /// that don't exist yet because you have not called BuildGraph, set this to true. - /// - void RegisterInstance(Type type, object implementation, bool overwrite = false, bool deferInject = false); + void RegisterInstance(Type type, object implementation, bool overwrite = false); /// /// Clear all services and types. diff --git a/Robust.Shared/IoC/IoCManager.cs b/Robust.Shared/IoC/IoCManager.cs index b3e27bdf0..868198bd6 100644 --- a/Robust.Shared/IoC/IoCManager.cs +++ b/Robust.Shared/IoC/IoCManager.cs @@ -165,17 +165,12 @@ namespace Robust.Shared.IoC /// If true, do not throw an if an interface is already registered, /// replace the current implementation instead. /// - /// - /// Defer field injection until is called. - /// If this is false, dependencies will be immediately injected. If the registered type requires dependencies - /// that don't exist yet because you have not called BuildGraph, set this to true. - /// - public static void RegisterInstance(object implementation, bool overwrite = false, bool deferInject = false) + public static void RegisterInstance(object implementation, bool overwrite = false) where TInterface : class { DebugTools.Assert(_container.IsValueCreated, NoContextAssert); - _container.Value!.RegisterInstance(implementation, overwrite, deferInject); + _container.Value!.RegisterInstance(implementation, overwrite); } /// diff --git a/Robust.UnitTesting/Shared/IoC/DependencyCollectionTest.cs b/Robust.UnitTesting/Shared/IoC/DependencyCollectionTest.cs new file mode 100644 index 000000000..503413426 --- /dev/null +++ b/Robust.UnitTesting/Shared/IoC/DependencyCollectionTest.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using Robust.Shared.IoC; + +namespace Robust.UnitTesting.Shared.IoC; + +[TestFixture] +[TestOf(typeof(DependencyCollection))] +[Parallelizable] +internal sealed class DependencyCollectionTest +{ + /// + /// Tests that registering two interfaces with the same implementation results in a single instance being shared. + /// + [Test] + public void TestRegisterSameImplementation() + { + var deps = new DependencyCollection(); + deps.Register(); + deps.Register(); + + deps.BuildGraph(); + + var a = deps.Resolve(); + var b = deps.Resolve(); + + Assert.That(a, Is.EqualTo(b), () => "A & B instances must be reference equal"); + } + + private interface IA + { + + } + + private interface IB + { + + } + + private sealed class C : IA, IB + { + + } +} diff --git a/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs b/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs index 1fc97cc59..3450b1572 100644 --- a/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs +++ b/Robust.UnitTesting/Shared/IoC/IoCManager_Test.cs @@ -135,6 +135,8 @@ namespace Robust.UnitTesting.Shared.IoC IoCManager.RegisterInstance(obj); + IoCManager.BuildGraph(); + Assert.That(IoCManager.Resolve(), Is.EqualTo(obj)); } @@ -151,10 +153,10 @@ namespace Robust.UnitTesting.Shared.IoC public void IoCRegInstancesBeforeBuildGraph() { var instanceA = new DependencyA(); - IoCManager.RegisterInstance(instanceA, deferInject: true); + IoCManager.RegisterInstance(instanceA); var instanceB = new DependencyB(); - IoCManager.RegisterInstance(instanceB, deferInject: true); + IoCManager.RegisterInstance(instanceB); IoCManager.BuildGraph(); @@ -173,7 +175,7 @@ namespace Robust.UnitTesting.Shared.IoC IoCManager.Register(); var instanceB = new DependencyB(); - IoCManager.RegisterInstance(instanceB, deferInject: true); + IoCManager.RegisterInstance(instanceB); IoCManager.BuildGraph(); @@ -189,7 +191,7 @@ namespace Robust.UnitTesting.Shared.IoC public void IoCRegInstanceDepBeforeBuildGraph() { var instanceB = new DependencyB(); - IoCManager.RegisterInstance(instanceB, deferInject: true); + IoCManager.RegisterInstance(instanceB); IoCManager.Register();