diff --git a/README.md b/README.md index 1ea1c16..1dd9d6e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ You can view the API on [FuGet](https://www.fuget.org/packages/Singulink.Reflect | Method | Mean | Error | StdDev | Gen0 | Allocated | |----------------------------------------- |------------:|----------:|----------:|-------:|----------:| | Activator_Object | 14.9644 ns | 0.2901 ns | 0.3341 ns | 0.0057 | 24 B | -| ObjectFactory_Object | 6.6270 ns | 0.0364 ns | 0.0323 ns | 0.0057 | 24 B | +| ObjectFactory_Object | 6.5780 ns | 0.0587 ns | 0.0549 ns | 0.0057 | 24 B | | ObjectFactory_Object_Delegate | 6.3640 ns | 0.0508 ns | 0.0451 ns | 0.0057 | 24 B | |----------------------------------------- |------------:|----------:|----------:|-------:|----------:| | Activator_ObjectPrivateCtor | 11.9688 ns | 0.0894 ns | 0.0793 ns | 0.0057 | 24 B | diff --git a/Source/Singulink.Reflection.ObjectFactory.Tests/GetActivatorTests.cs b/Source/Singulink.Reflection.ObjectFactory.Tests/GetActivatorTests.cs index 114d684..d662593 100644 --- a/Source/Singulink.Reflection.ObjectFactory.Tests/GetActivatorTests.cs +++ b/Source/Singulink.Reflection.ObjectFactory.Tests/GetActivatorTests.cs @@ -1,4 +1,5 @@ using System; +using System.CodeDom; using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; @@ -8,32 +9,32 @@ namespace Singulink.Reflection.Tests; public class GetActivatorTests { [TestMethod] - public void GetPrivateNonDefaultActivator() + public void PrivateParamActivation() { - Assert.ThrowsException(() => _ = ObjectFactory.GetActivator()); - Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorDelegate>(typeof(NoDefaultConstructor))); + Assert.ThrowsException(() => _ = ObjectFactory.GetActivator()); + Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorDelegate>(typeof(PrivateParamCtor))); - var factory1 = ObjectFactory.GetActivatorByDelegate>(typeof(NoDefaultConstructor), true); - var factory2 = ObjectFactory.GetActivator(true); + var factory1 = ObjectFactory.GetActivatorByDelegate>(typeof(PrivateParamCtor), true); + var factory2 = ObjectFactory.GetActivator(true); - ((NoDefaultConstructor)factory1.Invoke("test")).ArgValue.ShouldBe("test"); + ((PrivateParamCtor)factory1.Invoke("test")).ArgValue.ShouldBe("test"); factory2.Invoke("test").ArgValue.ShouldBe("test"); } [TestMethod] - public void GetPrivateDefaultActivator() + public void PrivateDefaultCtorActivation() { - Assert.ThrowsException(() => _ = ObjectFactory.GetActivator()); + Assert.ThrowsException(() => _ = ObjectFactory.GetActivator()); - var factory1 = ObjectFactory.GetActivatorByDelegate>(typeof(PrivateDefaultConstructor), true); - var factory2 = ObjectFactory.GetActivator(true); + var factory1 = ObjectFactory.GetActivatorByDelegate>(typeof(PrivateDefaultCtor), true); + var factory2 = ObjectFactory.GetActivator(true); - ((PrivateDefaultConstructor)factory1.Invoke()).InitializerCalled.ShouldBe(true); - factory2.Invoke().InitializerCalled.ShouldBe(true); + factory1.Invoke().ShouldBeOfType(); + factory2.Invoke().ShouldNotBeNull(); } [TestMethod] - public void GetStructActivator() + public void TupleActivation() { var factory = ObjectFactory.GetActivator<(string? Value, int Number)>(); factory.Invoke().ShouldBe(default); @@ -48,7 +49,75 @@ public void GetStructActivator() [TestMethod] public void InvalidReturnType() { - Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorByDelegate>(typeof(PrivateDefaultConstructor), true)); - Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorDelegate>(typeof(NoDefaultConstructor))); + Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorByDelegate>(typeof(PrivateDefaultCtor), true)); + Assert.ThrowsException(() => _ = ObjectFactory.GetActivatorDelegate>(typeof(PrivateParamCtor))); + } + + [TestMethod] + public void ShouldReturnObjects() + { + ObjectFactory.CreateInstance().ShouldNotBeNull(); + ObjectFactory.CreateInstance(true).ShouldNotBeNull(); + + ObjectFactory.GetActivator().Invoke().ShouldNotBeNull(); + ObjectFactory.GetActivator(true).Invoke().ShouldNotBeNull(); + ObjectFactory.GetActivator().Invoke("test").ShouldNotBeNull(); + + ObjectFactory.GetActivator().Invoke(); + ObjectFactory.GetActivator().Invoke(1, 2).ShouldBe(new ParamStruct(1, 2)); + } + + [TestMethod] + public void ShouldThrowMissingMethod() + { + Should.Throw(() => ObjectFactory.CreateInstance()); + Should.Throw(() => ObjectFactory.CreateInstance(true)); + Should.Throw(() => ObjectFactory.CreateInstance(true)); + + Should.Throw(() => ObjectFactory.GetActivator()); + Should.Throw(() => ObjectFactory.GetActivator()); + Should.Throw(() => ObjectFactory.GetActivator()); + + Should.Throw(() => ObjectFactory.GetActivator()); + } + + public class PublicDefaultCtor + { + } + + public class PublicParamCtor + { + public PublicParamCtor(string value) { } + } + + public class PrivateDefaultCtor + { + private PrivateDefaultCtor() + { + } + } + + public class PrivateParamCtor + { + public bool InitializerCalled { get; } = true; + + public string ArgValue { get; } + + private PrivateParamCtor(string argValue) + { + ArgValue = argValue; + } + } + + public struct ParamStruct + { + public long Value; + public long Value2; + + public ParamStruct(long value, long value2) + { + Value = value; + Value2 = value2; + } } } diff --git a/Source/Singulink.Reflection.ObjectFactory.Tests/NoDefaultConstructor.cs b/Source/Singulink.Reflection.ObjectFactory.Tests/NoDefaultConstructor.cs deleted file mode 100644 index dedf15a..0000000 --- a/Source/Singulink.Reflection.ObjectFactory.Tests/NoDefaultConstructor.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Singulink.Reflection.Tests; - -public class NoDefaultConstructor -{ - public bool InitializerCalled { get; } = true; - - public string ArgValue { get; } - - private NoDefaultConstructor(string argValue) - { - ArgValue = argValue; - } -} \ No newline at end of file diff --git a/Source/Singulink.Reflection.ObjectFactory.Tests/PrivateDefaultConstructor.cs b/Source/Singulink.Reflection.ObjectFactory.Tests/PrivateDefaultConstructor.cs deleted file mode 100644 index 1daf1fe..0000000 --- a/Source/Singulink.Reflection.ObjectFactory.Tests/PrivateDefaultConstructor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Singulink.Reflection.Tests; - -public class PrivateDefaultConstructor -{ - public bool InitializerCalled { get; } = true; - - private PrivateDefaultConstructor() - { - } -} diff --git a/Source/Singulink.Reflection.ObjectFactory.Tests/UninitializedInstanceTests.cs b/Source/Singulink.Reflection.ObjectFactory.Tests/UninitializedInstanceTests.cs index e0e3269..2cb9ad5 100644 --- a/Source/Singulink.Reflection.ObjectFactory.Tests/UninitializedInstanceTests.cs +++ b/Source/Singulink.Reflection.ObjectFactory.Tests/UninitializedInstanceTests.cs @@ -10,7 +10,12 @@ public class UninitializedInstanceTests [TestMethod] public void GetsUninitialized() { - var v = ObjectFactory.CreateUninitializedInstance(); + var v = ObjectFactory.CreateUninitializedInstance(); v.InitializerCalled.ShouldBe(false); } + + public class Initializer + { + public bool InitializerCalled { get; } = true; + } } diff --git a/Source/Singulink.Reflection.ObjectFactory/DefaultActivator.cs b/Source/Singulink.Reflection.ObjectFactory/DefaultActivator.cs index f79e161..125ac77 100644 --- a/Source/Singulink.Reflection.ObjectFactory/DefaultActivator.cs +++ b/Source/Singulink.Reflection.ObjectFactory/DefaultActivator.cs @@ -21,11 +21,11 @@ public readonly struct DefaultActivator // * If running on JIT: always use delegate. // * If running on AOT: Activator.CreateInstance() is faster than the delegate so use it if there is a public ctor. // The delegate is faster than CreateInstance(Type). - private static readonly bool _useGenericActivator = typeof(T).IsValueType || + internal static readonly bool UseGenericSystemActivator = typeof(T).IsValueType || (!RuntimeFeature.IsDynamicCodeCompiled && typeof(T).GetConstructor(Type.EmptyTypes) != null); #endif - private readonly Func? _activator; + internal readonly Func? _activator; /// /// Initializes a new instance of the struct. @@ -49,7 +49,7 @@ internal DefaultActivator(Func activator) public Func AsDelegate() { #if !NETSTANDARD - if (_useGenericActivator) + if (UseGenericSystemActivator) return static () => Activator.CreateInstance(); #endif @@ -66,7 +66,7 @@ public Func AsDelegate() public T Invoke() { #if !NETSTANDARD - if (_useGenericActivator) + if (UseGenericSystemActivator) return Activator.CreateInstance(); #endif diff --git a/Source/Singulink.Reflection.ObjectFactory/ObjectFactory.cs b/Source/Singulink.Reflection.ObjectFactory/ObjectFactory.cs index 8608626..dbd31e8 100644 --- a/Source/Singulink.Reflection.ObjectFactory/ObjectFactory.cs +++ b/Source/Singulink.Reflection.ObjectFactory/ObjectFactory.cs @@ -23,28 +23,63 @@ public static class ObjectFactory private static class DefaultActivatorCache<[DynamicallyAccessedMembers(DefaultConstructors)] T> { + public static readonly Func? Activator = TryGetActivator(); public static readonly bool IsPublic = typeof(T).IsValueType || typeof(T).GetConstructor(Type.EmptyTypes) is not null; - public static readonly DefaultActivator Instance = GetActivator(true); + + private static Func? TryGetActivator() + { +#if !NETSTANDARD + if (DefaultActivator.UseGenericSystemActivator) + return null; +#endif + + try + { +#pragma warning disable IL2087 // Justification: Only default ctors needed. + return GetActivatorByDelegate>(typeof(T), true); +#pragma warning restore IL2087 + } + catch + { + return null; + } + } + } + + /// + /// Creates an object of the specified type using the public default constructor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T CreateInstance<[DynamicallyAccessedMembers(PublicDefaultConstructor)] T>() + { +#pragma warning disable IL2091 // Justification: Only public default ctor needed. + return CreateInstance(false); +#pragma warning restore IL2091 } /// /// Creates an object of the specified type using the default constructor, optionally calling a non-public constructor as well. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T CreateInstance<[DynamicallyAccessedMembers(DefaultConstructors)] T>(bool nonPublic = false) + public static T CreateInstance<[DynamicallyAccessedMembers(DefaultConstructors)] T>(bool nonPublic) { #if !NETSTANDARD - if (!RuntimeFeature.IsDynamicCodeCompiled && !nonPublic) - return Activator.CreateInstance(); - - if (typeof(T).IsValueType) + if (DefaultActivator.UseGenericSystemActivator) return Activator.CreateInstance(); #endif if (!DefaultActivatorCache.IsPublic && !nonPublic) ThrowNonPublicConstructorException(typeof(T)); - return DefaultActivatorCache.Instance.Invoke(); + var activator = DefaultActivatorCache.Activator; + + if (activator == null) + { + Throw(); + static void Throw() => throw new MissingMethodException($"No parameterless constructor defined for type '{typeof(T)}'."); + } + + return DefaultActivatorCache.Activator!(); } /// @@ -223,7 +258,8 @@ public static TDelegate GetActivatorByDelegate([DynamicallyAccessedMe return (TDelegate)info.Activator; } - private static (TDelegate Activator, bool IsPublic) CreateActivator([DynamicallyAccessedMembers(AllConstructors)] Type objectType, bool nonPublic) where TDelegate : Delegate + private static (TDelegate Activator, bool IsPublic) CreateActivator([DynamicallyAccessedMembers(AllConstructors)] Type objectType, bool nonPublic) + where TDelegate : Delegate { #pragma warning disable IL2090 // Justification: Delegates always generate metadata for the Invoke method. var delegateInfo = typeof(TDelegate).GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); @@ -259,7 +295,9 @@ private static (TDelegate Activator, bool IsPublic) CreateActivator([ else { const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - ConstructorInfo constructor = objectType.GetConstructor(bindingFlags, null, parameterTypes, null) ?? throw new MissingMethodException("A matching constructor was not found."); + + ConstructorInfo constructor = objectType.GetConstructor(bindingFlags, null, parameterTypes, null) + ?? throw new MissingMethodException("A matching constructor was not found."); isPublic = constructor.IsPublic;