Skip to content

Commit

Permalink
Add tests, CreateInstance parameterless overload, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mikernet committed Feb 8, 2024
1 parent dd29b0b commit 1eed97a
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 57 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.CodeDom;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

Expand All @@ -8,32 +9,32 @@ namespace Singulink.Reflection.Tests;
public class GetActivatorTests
{
[TestMethod]
public void GetPrivateNonDefaultActivator()
public void PrivateParamActivation()
{
Assert.ThrowsException<MissingMethodException>(() => _ = ObjectFactory.GetActivator<NoDefaultConstructor>());
Assert.ThrowsException<MissingMethodException>(() => _ = ObjectFactory.GetActivatorDelegate<Func<string, NoDefaultConstructor>>(typeof(NoDefaultConstructor)));
Assert.ThrowsException<MissingMethodException>(() => _ = ObjectFactory.GetActivator<PrivateParamCtor>());
Assert.ThrowsException<MissingMethodException>(() => _ = ObjectFactory.GetActivatorDelegate<Func<string, PrivateParamCtor>>(typeof(PrivateParamCtor)));

var factory1 = ObjectFactory.GetActivatorByDelegate<Func<string, object>>(typeof(NoDefaultConstructor), true);
var factory2 = ObjectFactory.GetActivator<string, NoDefaultConstructor>(true);
var factory1 = ObjectFactory.GetActivatorByDelegate<Func<string, object>>(typeof(PrivateParamCtor), true);
var factory2 = ObjectFactory.GetActivator<string, PrivateParamCtor>(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<MissingMethodException>(() => _ = ObjectFactory.GetActivator<PrivateDefaultConstructor>());
Assert.ThrowsException<MissingMethodException>(() => _ = ObjectFactory.GetActivator<PrivateDefaultCtor>());

var factory1 = ObjectFactory.GetActivatorByDelegate<Func<object>>(typeof(PrivateDefaultConstructor), true);
var factory2 = ObjectFactory.GetActivator<PrivateDefaultConstructor>(true);
var factory1 = ObjectFactory.GetActivatorByDelegate<Func<object>>(typeof(PrivateDefaultCtor), true);
var factory2 = ObjectFactory.GetActivator<PrivateDefaultCtor>(true);

((PrivateDefaultConstructor)factory1.Invoke()).InitializerCalled.ShouldBe(true);
factory2.Invoke().InitializerCalled.ShouldBe(true);
factory1.Invoke().ShouldBeOfType<PrivateDefaultCtor>();
factory2.Invoke().ShouldNotBeNull();
}

[TestMethod]
public void GetStructActivator()
public void TupleActivation()
{
var factory = ObjectFactory.GetActivator<(string? Value, int Number)>();
factory.Invoke().ShouldBe(default);
Expand All @@ -48,7 +49,75 @@ public void GetStructActivator()
[TestMethod]
public void InvalidReturnType()
{
Assert.ThrowsException<InvalidCastException>(() => _ = ObjectFactory.GetActivatorByDelegate<Func<string>>(typeof(PrivateDefaultConstructor), true));
Assert.ThrowsException<InvalidCastException>(() => _ = ObjectFactory.GetActivatorDelegate<Func<string, string>>(typeof(NoDefaultConstructor)));
Assert.ThrowsException<InvalidCastException>(() => _ = ObjectFactory.GetActivatorByDelegate<Func<string>>(typeof(PrivateDefaultCtor), true));
Assert.ThrowsException<InvalidCastException>(() => _ = ObjectFactory.GetActivatorDelegate<Func<string, string>>(typeof(PrivateParamCtor)));
}

[TestMethod]
public void ShouldReturnObjects()
{
ObjectFactory.CreateInstance<PublicDefaultCtor>().ShouldNotBeNull();
ObjectFactory.CreateInstance<PrivateDefaultCtor>(true).ShouldNotBeNull();

ObjectFactory.GetActivator<PublicDefaultCtor>().Invoke().ShouldNotBeNull();
ObjectFactory.GetActivator<PrivateDefaultCtor>(true).Invoke().ShouldNotBeNull();
ObjectFactory.GetActivator<string, PublicParamCtor>().Invoke("test").ShouldNotBeNull();

ObjectFactory.GetActivator<ParamStruct>().Invoke();
ObjectFactory.GetActivator<long, long, ParamStruct>().Invoke(1, 2).ShouldBe(new ParamStruct(1, 2));
}

[TestMethod]
public void ShouldThrowMissingMethod()
{
Should.Throw<MissingMemberException>(() => ObjectFactory.CreateInstance<PrivateDefaultCtor>());
Should.Throw<MissingMemberException>(() => ObjectFactory.CreateInstance<PrivateParamCtor>(true));
Should.Throw<MissingMemberException>(() => ObjectFactory.CreateInstance<PublicParamCtor>(true));

Should.Throw<MissingMemberException>(() => ObjectFactory.GetActivator<PrivateDefaultCtor>());
Should.Throw<MissingMemberException>(() => ObjectFactory.GetActivator<PrivateParamCtor>());
Should.Throw<MissingMemberException>(() => ObjectFactory.GetActivator<PublicParamCtor>());

Should.Throw<MissingMemberException>(() => ObjectFactory.GetActivator<long, ParamStruct>());
}

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;
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ public class UninitializedInstanceTests
[TestMethod]
public void GetsUninitialized()
{
var v = ObjectFactory.CreateUninitializedInstance<PrivateDefaultConstructor>();
var v = ObjectFactory.CreateUninitializedInstance<Initializer>();
v.InitializerCalled.ShouldBe(false);
}

public class Initializer
{
public bool InitializerCalled { get; } = true;
}
}
8 changes: 4 additions & 4 deletions Source/Singulink.Reflection.ObjectFactory/DefaultActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ public readonly struct DefaultActivator<T>
// * If running on JIT: always use delegate.
// * If running on AOT: Activator.CreateInstance<T>() 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<T>? _activator;
internal readonly Func<T>? _activator;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultActivator{T}"/> struct.
Expand All @@ -49,7 +49,7 @@ internal DefaultActivator(Func<T> activator)
public Func<T> AsDelegate()
{
#if !NETSTANDARD
if (_useGenericActivator)
if (UseGenericSystemActivator)
return static () => Activator.CreateInstance<T>();
#endif

Expand All @@ -66,7 +66,7 @@ public Func<T> AsDelegate()
public T Invoke()
{
#if !NETSTANDARD
if (_useGenericActivator)
if (UseGenericSystemActivator)
return Activator.CreateInstance<T>();
#endif

Expand Down
56 changes: 47 additions & 9 deletions Source/Singulink.Reflection.ObjectFactory/ObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,63 @@ public static class ObjectFactory

private static class DefaultActivatorCache<[DynamicallyAccessedMembers(DefaultConstructors)] T>
{
public static readonly Func<T>? Activator = TryGetActivator();
public static readonly bool IsPublic = typeof(T).IsValueType || typeof(T).GetConstructor(Type.EmptyTypes) is not null;
public static readonly DefaultActivator<T> Instance = GetActivator<T>(true);

private static Func<T>? TryGetActivator()
{
#if !NETSTANDARD
if (DefaultActivator<T>.UseGenericSystemActivator)
return null;
#endif

try
{
#pragma warning disable IL2087 // Justification: Only default ctors needed.
return GetActivatorByDelegate<Func<T>>(typeof(T), true);
#pragma warning restore IL2087
}
catch
{
return null;
}
}
}

/// <summary>
/// Creates an object of the specified type using the public default constructor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T CreateInstance<[DynamicallyAccessedMembers(PublicDefaultConstructor)] T>()
{
#pragma warning disable IL2091 // Justification: Only public default ctor needed.
return CreateInstance<T>(false);
#pragma warning restore IL2091
}

/// <summary>
/// Creates an object of the specified type using the default constructor, optionally calling a non-public constructor as well.
/// </summary>
[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<T>();

if (typeof(T).IsValueType)
if (DefaultActivator<T>.UseGenericSystemActivator)
return Activator.CreateInstance<T>();
#endif

if (!DefaultActivatorCache<T>.IsPublic && !nonPublic)
ThrowNonPublicConstructorException(typeof(T));

return DefaultActivatorCache<T>.Instance.Invoke();
var activator = DefaultActivatorCache<T>.Activator;

if (activator == null)
{
Throw();
static void Throw() => throw new MissingMethodException($"No parameterless constructor defined for type '{typeof(T)}'.");
}

return DefaultActivatorCache<T>.Activator!();
}

/// <summary>
Expand Down Expand Up @@ -223,7 +258,8 @@ public static TDelegate GetActivatorByDelegate<TDelegate>([DynamicallyAccessedMe
return (TDelegate)info.Activator;
}

private static (TDelegate Activator, bool IsPublic) CreateActivator<TDelegate>([DynamicallyAccessedMembers(AllConstructors)] Type objectType, bool nonPublic) where TDelegate : Delegate
private static (TDelegate Activator, bool IsPublic) CreateActivator<TDelegate>([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);
Expand Down Expand Up @@ -259,7 +295,9 @@ private static (TDelegate Activator, bool IsPublic) CreateActivator<TDelegate>([
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;

Expand Down

0 comments on commit 1eed97a

Please sign in to comment.