TryAtSoftware.Extensions.Reflection
1.1.1-alpha.3
Prefix Reserved
See the version list below for details.
dotnet add package TryAtSoftware.Extensions.Reflection --version 1.1.1-alpha.3
NuGet\Install-Package TryAtSoftware.Extensions.Reflection -Version 1.1.1-alpha.3
<PackageReference Include="TryAtSoftware.Extensions.Reflection" Version="1.1.1-alpha.3" />
paket add TryAtSoftware.Extensions.Reflection --version 1.1.1-alpha.3
#r "nuget: TryAtSoftware.Extensions.Reflection, 1.1.1-alpha.3"
// Install TryAtSoftware.Extensions.Reflection as a Cake Addin #addin nuget:?package=TryAtSoftware.Extensions.Reflection&version=1.1.1-alpha.3&prerelease // Install TryAtSoftware.Extensions.Reflection as a Cake Tool #tool nuget:?package=TryAtSoftware.Extensions.Reflection&version=1.1.1-alpha.3&prerelease
About the project
TryAtSoftware.Extensions
is a library containing extension methods and utility components that should simplify (and optimize) some common operations with reflection.
About us
Try At Software
is a software development company based in Bulgaria. We are mainly using dotnet
technologies (C#
, ASP.NET Core
, Entity Framework Core
, etc.) and our main idea is to provide a set of tools that can simplify the majority of work a developer does on a daily basis.
Getting started
TypeNames
This is an utility component that should construct and cache a beautified name for a type. There are two ways to use it:
- Throughout the generic
TypeNames<T>
class and theValue
property; - Throughout the non-generic
TypeNames
class and theGet(type)
method.
The main idea behind this component is to provide efficiently a meaningful and readable type name (including open generics).
Here is a comparison between the type.ToString()
and TypeNames.Get(type)
:
type | type.ToString() | TypeNames.Get(type) |
---|---|---|
Task<int> |
System.Threading.Tasks.Task`1[System.Int32] | Task<Int32> |
Task<List<long>> |
System.Collections.Generic.List`1[System.Threading.Tasks.Task`1[System.Int64]] | List<Task<Person>> |
Task<> |
System.Threading.Tasks.Task`1[TResult] | Task<TResult> |
List<> |
System.Collections.Generic.List`1[T] | List<T> |
IMembersBinder
This is an interface defining the structure of an utility component exposing information about a specific subset of the members for a given type.
There are two classes implementing this interface - a non-generic MembersBinder
and a generic MembersBinder<T>
.
They accept the following parameters throughout their constructors:
isValid
: A filtering function that should determine whether or not a member should be included within the final result set. If no value is provided for this parameter, every member will be included.keySelector
: A function mapping each each member to unique key. If no value is provided to this parameter, the name of the member will be used (which means that in case of overrides or members with the same name there will be issues)bindingFlags
: The binding flags used to control the member search throughout reflection.
Example:
IMembersBinder binder = new MembersBinder<TEntity>(IsValidMember, BindingFlags.Public | BindingFlags.Instance);
// Equivalent to:
// IMembersBinder = new MembersBinder(typeof(TEntity), IsValidMember, BindingFlags.Public | BindingFlags.Instance);
static bool IsValidMember(MemberInfo memberInfo)
=> memberInfo switch
{
PropertyInfo pi => pi.CanWrite,
FieldInfo _ => true,
_ => false
};
// Now every discovered member for the `TEntity` type will be mapped against its name throughout the `binder.MemberInfos` dictionary.
Members binder and extended interfaces
It is known that whenever the .GetMembers()
method is invoked for an interface type, none of the members defined in extended interfaces will be returned.
The default implementations of the IMembersBinder
method overcome this and will include members from all of the extended interfaces by recursively retrieving all of them.
That being said, it is obvious that the ReflectedType
in this case may not equal the Type
of the IMembersBinder
instance.
Expression extensions
ConstructPropertyAccessor
This is an extension method that should construct an expression for accessing the value of a specific property.
Usually, it is a good practice to minimize the reflection calls in code. One way of achieving this is throughout expressions (that are constructed and compiled only once for the lifetime of a program).
This expression method can be easily used with the IMembersBinder
we described in the previous chapter.
Conversions are also handled, e.g. a common use case is to retrieve the values of all properties by boxing them as object
instances - this can be achieved without any additional configurations as long as the required conversion can be executed.
Example:
IMembersBinder binder = new MembersBinder<TEntity>(x => x is PropertyInfo { CanRead: true}, BindingFlags.Public | BindingFlags.Instance);
List<Expression<Func<TEntity, object>>> valueAccessors = new List<Expression<Func<TEntity, object>>>();
foreach (var (_, memberInfo) in binder.MemberInfos)
{
var propertyInfo = memberInfo as PropertyInfo;
var accessor = propertyInfo.ConstructPropertyAccessor<TEntity, object>();
valueAccessors.Add(accessor);
}
ConstructPropertySetter
This is an extension method that should construct an expression for setting the value of a specific property.
Usually, it is a good practice to minimize the reflection calls in code. One way of achieving this is throughout expressions (that are constructed and compiled only once for the lifetime of a program).
This expression method can be easily used with the IMembersBinder
we described in the previous chapter.
Conversions are also handled, e.g. a common use case is to set the values of all properties after unboxing object
instances - this can be achieved without any additional configurations as long as the required conversion can be executed.
Example:
IMembersBinder binder = new MembersBinder<TEntity>(x => x is PropertyInfo { CanWrite: true}, BindingFlags.Public | BindingFlags.Instance);
List<Expression<Action<TEntity, object>>> valueSetters = new List<Expression<Action<TEntity, object>>>();
foreach (var (_, memberInfo) in binder.MemberInfos)
{
var propertyInfo = memberInfo as PropertyInfo;
var setter = propertyInfo.ConstructPropertySetter<TEntity, object>();
valueSetters.Add(setter);
}
ConstructObjectInitializer
This is an extension method that should construct an expression for instantiating an object using a specific constructor.
Usually, it is a good practice to minimize the reflection calls in code. One way of achieving this is throughout expressions (that are constructed and compiled only once for the lifetime of a program).
This expression method can be easily used with the IMembersBinder
we described in the previous chapter.
This methods accept one optional parameter called includeParametersCountValidation
. It indicates whether or not the subsequently constructed expression should include a check to validate the correct count of provided values.
An expression constructed by this extension method can be compiled to a function that accepts an array of values that correspond to the parameters of the extended ConstructorInfo
instance.
If any of the parameters is optional and its default value should be used, the corresponding element (from the provided array) must be null
.
Example:
IMembersBinder binder = new MembersBinder<TEntity>(x => x is ConstructorInfo, x => PrepareConstructorKey((ConstructorInfo)x), BindingFlags.Public | BindingFlags.Instance);
List<Expression<Func<object?[], TEntity>>> objectInitializers = new List<Expression<Func<object?[], TEntity>>>();
foreach (var (_, memberInfo) in binder.MemberInfos)
{
var constructorInfo = memberInfo as ConstructorInfo;
var objectInitializer = constructorInfo.ConstructObjectInitializer<TEntity>();
objectInitializers.Add(objectInitializer);
}
static string PrepareConstructorKey(ConstructorInfo constructorInfo)
{
var parameterTypeNames = constructorInfo.GetParameters().Select(p => TypeNames.Get(p.ParameterType));
return $"Constructor[{string.Join(", ", parameterTypeNames)}]";
}
Generic extensions
ExtractGenericParametersSetup
This method will produce a dictionary where against the name of a generic parameter is stored the actual type associated with that parameter according to some external configuration. This method should be used whenever each generic parameter is uniquely identified with a certain attribute. All entries within the external configuration map should have as a key the attribute type and as a value the type that should be substituted for a generic parameter decorated with the corresponding attribute.
If there are none or multiple attributes for a generic parameter, this method will throw an exception. If the external configuration is missing some attribute type, an exception will be thrown as well.
Example:
public class MyType<[KeyType] TKey, [ValueType] TValue> {}
IDictionary<Type, Type> typesMap = new Dictionary<Type, Type> { { typeof(KeyTypeAttribute), typeof(int) }, { typeof(ValueTypeAttribute), typeof(string) } };
/// should return { "TKey": typeof(int), "TValue": typeof(string) }
IDictionary<string, Type> genericParametersSetup = ExtractGenericParametersSetup(typeof(MyType<,>), typesMap);
MakeGenericType
Use this method to make the extended type generic using a parameters setup.
Example:
public class MyType<[KeyType] TKey, [ValueType] TValue> {}
IDictionary<Type, Type> typesMap = new Dictionary<Type, Type> { { typeof(KeyTypeAttribute), typeof(int) }, { typeof(ValueTypeAttribute), typeof(string) } };
IDictionary<string, Type> genericParametersSetup = ExtractGenericParametersSetup(typeof(MyType<,>), typesMap);
Type genericType = typeof(MyType<,>).MakeGenericType(genericParametersSetup);
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- TryAtSoftware.Extensions.Collections (>= 1.0.0)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on TryAtSoftware.Extensions.Reflection:
Package | Downloads |
---|---|
TryAtSoftware.Randomizer
This package should be used to make your object initialization process in tests cleaner and easier than ever before. |
|
TryAtSoftware.Equalizer
This package should be used to make your assertion process in tests cleaner and easier than ever before. |
|
TryAtSoftware.CleanTests
This package should be used to generate multiple combinations for a test case (according to the applied setup) and thus make the process of writing tests cleaner and easier than ever before. |
|
TryAtSoftware.Extensions.DependencyInjection
This is an internal package containing extension methods and utility components that should simplify some common operations with dependency injection. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.1.2 | 643 | 11/15/2023 |
1.1.2-alpha.2 | 101 | 9/26/2023 |
1.1.2-alpha.1 | 84 | 7/7/2023 |
1.1.1 | 2,163 | 6/13/2023 |
1.1.1-alpha.6 | 90 | 6/3/2023 |
1.1.1-alpha.5 | 77 | 5/28/2023 |
1.1.1-alpha.4 | 84 | 4/24/2023 |
1.1.1-alpha.3 | 101 | 3/21/2023 |
1.1.1-alpha.2 | 1,549 | 1/24/2023 |
1.1.1-alpha.1 | 123 | 1/13/2023 |
1.1.0 | 489 | 1/1/2023 |
1.0.0 | 875 | 12/16/2022 |
1.0.0-alpha.8 | 142 | 7/4/2022 |
1.0.0-alpha.7 | 191 | 4/21/2022 |
1.0.0-alpha.6 | 141 | 4/9/2022 |
1.0.0-alpha.5 | 149 | 1/29/2022 |
1.0.0-alpha.4 | 148 | 12/11/2021 |
1.0.0-alpha.3 | 138 | 12/11/2021 |
1.0.0-alpha.2 | 293 | 12/11/2021 |
1.0.0-alpha.1 | 358 | 12/11/2021 |