Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feature: Method to Set Instance #45

Merged
merged 9 commits into from
Oct 9, 2024
43 changes: 43 additions & 0 deletions TJC.Singleton.Tests/Mocks/Valid/MockSingletonPredefinedTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace TJC.Singleton.Tests.Mocks.Valid;

/// <summary>
/// This singleton is an example of the proper intended use of the <see cref="SingletonBase{TMyClass}"/>.
/// <para>This singleton has predefined types, similar to what may be seen within a settings singleton.</para>
/// </summary>
internal class MockSingletonPreDefinedTypes : SingletonBase<MockSingletonPreDefinedTypes>
{
#region Constructor

private MockSingletonPreDefinedTypes() { }

#endregion

#region Predefined Types

public static MockSingletonPreDefinedTypes Default { get; } = new() { Setting1 = "Setting1", Setting2 = "Setting2" };

public static MockSingletonPreDefinedTypes Empty { get; } = new() { Setting1 = string.Empty, Setting2 = string.Empty };

public static MockSingletonPreDefinedTypes Alphabet { get; } = new() { Setting1 = "ABC", Setting2 = "DEF" };

public static MockSingletonPreDefinedTypes Numbers { get; } = new() { Setting1 = "123", Setting2 = "456" };

public static MockSingletonPreDefinedTypes Symbols { get; } = new() { Setting1 = "!@#", Setting2 = "$%^" };

#endregion

#region Properties

public string Setting1 { get; private set; } = "Setting1";

public string Setting2 { get; private set; } = "Setting2";

#endregion

#region Methods

public static void SetInstance(MockSingletonPreDefinedTypes value) =>
SetBaseInstance(value);

#endregion
}
34 changes: 34 additions & 0 deletions TJC.Singleton.Tests/Tests/SetInstance/SetInstanceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace TJC.Singleton.Tests.Tests.SetInstance;

[TestClass]
public class SetInstanceTests
{
[TestMethod]
public void SetInstance_PredefinedTypes()
{
MockSingletonPreDefinedTypes.SetInstance(MockSingletonPreDefinedTypes.Default);

Assert.AreEqual("Setting1", MockSingletonPreDefinedTypes.Instance.Setting1);
Assert.AreEqual("Setting2", MockSingletonPreDefinedTypes.Instance.Setting2);

MockSingletonPreDefinedTypes.SetInstance(MockSingletonPreDefinedTypes.Empty);

Assert.AreEqual(string.Empty, MockSingletonPreDefinedTypes.Instance.Setting1);
Assert.AreEqual(string.Empty, MockSingletonPreDefinedTypes.Instance.Setting2);

MockSingletonPreDefinedTypes.SetInstance(MockSingletonPreDefinedTypes.Alphabet);

Assert.AreEqual("ABC", MockSingletonPreDefinedTypes.Instance.Setting1);
Assert.AreEqual("DEF", MockSingletonPreDefinedTypes.Instance.Setting2);

MockSingletonPreDefinedTypes.SetInstance(MockSingletonPreDefinedTypes.Numbers);

Assert.AreEqual("123", MockSingletonPreDefinedTypes.Instance.Setting1);
Assert.AreEqual("456", MockSingletonPreDefinedTypes.Instance.Setting2);

MockSingletonPreDefinedTypes.SetInstance(MockSingletonPreDefinedTypes.Symbols);

Assert.AreEqual("!@#", MockSingletonPreDefinedTypes.Instance.Setting1);
Assert.AreEqual("$%^", MockSingletonPreDefinedTypes.Instance.Setting2);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
namespace TJC.Singleton.Exceptions;

/// <summary>
/// This exception is thrown when a singleton class has an invalid constructor.
/// </summary>
/// <param name="message"></param>
public class InvalidSingletonConstructorException(string? message) : Exception(message);
4 changes: 4 additions & 0 deletions TJC.Singleton/Exceptions/SingletonInitializationException.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
namespace TJC.Singleton.Exceptions;

/// <summary>
/// This exception is thrown when a singleton fails to initialize.
/// </summary>
/// <param name="message"></param>
public class SingletonInitializationException(string? message) : Exception(message);
26 changes: 26 additions & 0 deletions TJC.Singleton/Factories/SingletonFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ private class PlaceholderSingleton : SingletonBase<PlaceholderSingleton>;

#endregion

/// <summary>
/// Instantiate all singletons in the current app domain.
/// </summary>
/// <param name="trace"></param>
/// <param name="throwIfFailed"></param>
/// <exception cref="Exception"></exception>
public static void InstantiateAll(bool trace = true,
bool throwIfFailed = false)
{
Expand All @@ -31,6 +37,10 @@ public static void InstantiateAll(bool trace = true,
throw new Exception($"{string.Join(", ", failedToInstantiate)}");
}

/// <summary>
/// Get all singleton types in the current app domain.
/// </summary>
/// <returns></returns>
public static List<Type> GetSingletonTypes()
{
var singletons = new List<Type>();
Expand All @@ -53,6 +63,22 @@ public static List<Type> GetSingletonTypes()
return singletons;
}

/// <summary>
/// Instantiates a singleton of type <seealso cref="T"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="trace"></param>
/// <returns></returns>
public static bool Instantiate<T>(bool trace) =>
Instantiate(typeof(T), trace);

/// <summary>
/// Instantiates a singleton of given type.
/// </summary>
/// <param name="singleton"></param>
/// <param name="trace"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
private static bool Instantiate(this Type singleton, bool trace)
{
if (trace)
Expand Down
21 changes: 21 additions & 0 deletions TJC.Singleton/Helpers/SingletonConstructorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ public static class SingletonConstructorHelpers
{
#region Get Singlet Constructor

/// <summary>
/// Gets the singleton constructor for the type <seealso cref="T"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ConstructorInfo GetSingletonConstructor<T>() =>
GetSingletonConstructor(typeof(T));

/// <summary>
/// Gets the singleton constructor for the type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <exception cref="InvalidSingletonConstructorException"></exception>
public static ConstructorInfo GetSingletonConstructor(Type type)
{
// Ensure there is no public constructor
Expand All @@ -28,9 +39,19 @@ public static ConstructorInfo GetSingletonConstructor(Type type)

#region Check if Singleton has Valid Constructor

/// <summary>
/// Checks if a singleton of type <seealso cref="T"/> has a valid constructor.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static bool HasValidSingletonConstructor<T>() =>
HasValidSingletonConstructor(typeof(T));

/// <summary>
/// Checks if the singleton has a valid constructor.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool HasValidSingletonConstructor(Type type)
{
try
Expand Down
6 changes: 6 additions & 0 deletions TJC.Singleton/Helpers/SingletonIdentifierHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

public static class SingletonIdentifierHelpers
{
/// <summary>
/// Determines if a type is a concrete singleton.
/// <para>I.e. If it is a <c>non-abstract class</c> that derives from <see cref="SingletonBase{TDerivedClass}"/>, or a class that does (recursively).</para>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsConcreteSingleton(Type type)
{
// Ensure type is a concrete class
Expand Down
29 changes: 28 additions & 1 deletion TJC.Singleton/SingletonBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,53 @@ public abstract class SingletonBase<TDerivedClass> where TDerivedClass : Singlet
{
#region Fields

private static readonly Lazy<TDerivedClass> _instance = new(CreateInstance);
/// <summary>
/// The private singleton instance.
/// </summary>
private static Lazy<TDerivedClass> _instance = new(CreateInstance);

#endregion

#region Properties

/// <summary>
/// The singleton instance.
/// </summary>
public static TDerivedClass Instance => _instance.Value;

/// <summary>
/// Check if the singleton has been instantiated.
/// </summary>
public static bool IsInstantiated => _instance.IsValueCreated;

#endregion

#region Methods

/// <summary>
/// This method is called when the singleton is first accessed.
/// <para>It is used to create the singleton instance by calling the constructor of the derived class.</para>
/// </summary>
/// <returns></returns>
/// <exception cref="SingletonInitializationException"></exception>
private static TDerivedClass CreateInstance()
{
// Use reflection to create an instance of the derived class.
var ctor = SingletonConstructorHelpers.GetSingletonConstructor<TDerivedClass>();
return (TDerivedClass)ctor.Invoke(null) ??
throw new SingletonInitializationException($"[{typeof(TDerivedClass)}] singleton failed to initialize");
}

/// <summary>
/// This method is used to set the singleton instance to a specific value.
/// <para>It is primarily used to set the instance to a predefined type.</para>
/// <para>For example, singletons are often used as settings, which have predefined types, such as <c>Default</c>, <c>Verbose</c>, <c>Silent</c>, etc.</para>
/// <para>This is set as <c>protected</c> so that it can only be accessed from within the singleton itself, since not all singletons should be able to be set.</para>
/// </summary>
/// <param name="value"></param>
protected static void SetBaseInstance(TDerivedClass value)
{
_instance = new(() => value);
}

#endregion
Expand Down
Loading