Skip to content

Instantly share code, notes, and snippets.

@altunsercan
Created April 6, 2024 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save altunsercan/8206692c72545dbf6997a0b3e174c9de to your computer and use it in GitHub Desktop.
Save altunsercan/8206692c72545dbf6997a0b3e174c9de to your computer and use it in GitHub Desktop.
Simple single file service locator
namespace Dependency;
public static class ServiceLocator
{
private static BaseServiceLocator instance;
private static BaseServiceLocator GetInstance()
{
return instance ??= new BaseServiceLocator();
}
public static T Resolve<T>() => GetInstance().Resolve<T>();
public static void RegisterProvider<T>(Func<T> providerDelegate) => GetInstance().RegisterProvider(providerDelegate);
public static void RegisterProvider<T>(Func<BaseServiceLocator, T> providerDelegate) => GetInstance().RegisterProvider(providerDelegate);
public static void RegisterCached<T>(T instance) => GetInstance().RegisterCached(instance);
}
public class BaseServiceLocator
{
private Dictionary<Type, Provider> providers = new ();
public void RegisterProvider<T>(Func<T> providerDelegate)
{
ThrowIfNullProvider(providerDelegate, typeof(T));
ThrowIfProviderExists(providers, typeof(T));
providers.Add(typeof(T), Provider.Create(providerDelegate));
}
public void RegisterProvider<T>(Func<BaseServiceLocator, T> providerDelegate)
{
ThrowIfNullProvider(providerDelegate, typeof(T));
ThrowIfProviderExists(providers, typeof(T));
providers.Add(typeof(T), Provider.Create(providerDelegate));
}
public void RegisterCached<T>(T instance)
{
var cachedProvider = () => instance;
RegisterProvider(cachedProvider);
}
public T Resolve<T>()
{
var provider = providers[typeof(T)];
return provider.Resolve<T>(serviceLocator: this);
}
private static void ThrowIfNullProvider(object providerDelegate, Type typeName) {
if (providerDelegate == null)
{
throw new NullProviderException(typeName);
}
}
private static void ThrowIfProviderExists(Dictionary<Type, Provider> providers, Type type) {
if (providers.ContainsKey(type))
{
throw new DuplicateProviderException(type);
}
}
private struct Provider
{
private object untypedDelegate;
private Provider(object untypedDelegate)
{
this.untypedDelegate = untypedDelegate;
}
public static Provider Create<T>(Func<T> providerDelegate) => new(providerDelegate);
public static Provider Create<T>(Func<BaseServiceLocator, T> providerDelegate) => new(providerDelegate);
public T Resolve<T>(BaseServiceLocator serviceLocator)
{
if (untypedDelegate is Func<BaseServiceLocator, T> typedDelegateWithLocator)
{
return typedDelegateWithLocator.Invoke(serviceLocator);
}
if (untypedDelegate is Func<T> typedDelegate)
{
return typedDelegate.Invoke();
}
throw new ProviderTypeResolutionException(typeof(T));
}
}
}
public class ProviderTypeResolutionException : Exception
{
public readonly Type Type;
public ProviderTypeResolutionException(Type type)
{
Type = type;
}
public override string Message => $"Cannot register null provider";
}
public class NullProviderException : Exception
{
public readonly Type Type;
public NullProviderException(Type type)
{
Type = type;
}
public override string Message => $"Cannot register null provider";
}
public class DuplicateProviderException : Exception
{
public readonly Type Type;
public DuplicateProviderException(Type type)
{
Type = type;
}
public override string Message => $"Cannot register duplicate provider";
}
using Dependency;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
namespace Test;
public class ServiceLocatorTest
{
[SetUp]
public void Setup()
{
}
[Test]
public void InvokeServiceLocatorProviderWithNoDependency()
{
var mockProviderDelegate = Substitute.For<Func<ITestFoo>>();
var serviceLocator = new BaseServiceLocator();
serviceLocator.RegisterProvider(mockProviderDelegate);
ITestFoo foo = serviceLocator.Resolve<ITestFoo>();
Assert.NotNull(foo);
}
[Test]
public void InvokeServiceLocatorProviderWithDependencies()
{
var mockDependencyProviderDelegate = Substitute.For<Func<BaseServiceLocator, ITestBar>>();
Func<BaseServiceLocator, ITestFoo> mockProviderDelegate = (locator)=> new TestFooWithBar(locator.Resolve<ITestBar>());
var serviceLocator = new BaseServiceLocator();
serviceLocator.RegisterProvider(mockDependencyProviderDelegate);
serviceLocator.RegisterProvider(mockProviderDelegate);
ITestFoo foo = serviceLocator.Resolve<ITestFoo>();
Assert.NotNull(foo);
}
[Test]
public void DontAllowMultipleProvidersForSameType()
{
Func<ITestFoo> providerDelegate = Substitute.For<Func<ITestFoo>>();
Func<ITestFoo> providerDelegate2 = Substitute.For<Func<ITestFoo>>();
var serviceLocator = new BaseServiceLocator();
serviceLocator.RegisterProvider<ITestFoo>(providerDelegate);
Assert.Throws<DuplicateProviderException>(() =>
{
serviceLocator.RegisterProvider(providerDelegate2);
});
}
[Test]
public void ShouldNotAllowNullProviders()
{
var serviceLocator = new BaseServiceLocator();
Func<ITestFoo> provider = null;
Assert.Throws<NullProviderException>(() =>
{
serviceLocator.RegisterProvider(provider);
});
Func<BaseServiceLocator, ITestFoo> providerWithDependency = null;
Assert.Throws<NullProviderException>(() =>
{
serviceLocator.RegisterProvider(providerWithDependency);
});
}
[Test]
public void RegisterSingletonProvider()
{
var mockFoo = Substitute.For<ITestFoo>();
var serviceLocator = new BaseServiceLocator();
serviceLocator.RegisterCached(mockFoo);
ITestFoo foo = serviceLocator.Resolve<ITestFoo>();
foo.Should().Be(mockFoo);
}
}
public interface ITestBar { }
public interface ITestFoo { }public class TestFooWithBar : ITestFoo
{
private readonly ITestBar bar;
public TestFooWithBar(ITestBar bar)
{
this.bar = bar;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment