2021-01-20 23:35:57 +08:00
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System ;
2021-01-31 01:25:25 +08:00
using System.Collections.Generic ;
using System.Diagnostics.CodeAnalysis ;
2021-01-20 23:35:57 +08:00
using System.Linq ;
using System.Linq.Expressions ;
2021-01-31 01:25:25 +08:00
using System.Reflection ;
2021-01-20 23:35:57 +08:00
namespace AntDesign.core.Helpers
{
public static class PropertyAccessHelper
{
2021-01-31 01:25:25 +08:00
public const string DefaultPathSeparator = "." ;
private const string Nullable_HasValue = "HasValue" ;
private const string Nullable_Value = "Value" ;
private const string CountPropertyName = "Count" ;
private const string GetItemMethodName = "get_Item" ;
#region Build not Nullable delegate
public static LambdaExpression BuildAccessPropertyLambdaExpression ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
return BuildAccessPropertyLambdaExpression ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
public static LambdaExpression BuildAccessPropertyLambdaExpression ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
var parameterExpression = Expression . Parameter ( type ) ;
var expression = AccessProperty ( parameterExpression , properties ) ;
return Expression . Lambda ( expression , parameterExpression ) ;
}
public static Expression < Func < TParam , TReturn > > BuildAccessPropertyLambdaExpression < TParam , TReturn > ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
{
return BuildAccessPropertyLambdaExpression < TParam , TReturn > ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static Expression < Func < TParam , TReturn > > BuildAccessPropertyLambdaExpression < TParam , TReturn > ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
var parameterExpression = Expression . Parameter ( type ) ;
var expression = AccessProperty ( parameterExpression , properties ) ;
return Expression . Lambda < Func < TParam , TReturn > > ( expression , parameterExpression ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
public static Expression < Func < TParam , TReturn > > BuildAccessPropertyLambdaExpression < TParam , TReturn > (
[NotNull] this ParameterExpression parameterExpression ,
[NotNull] string [ ] properties )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( parameterExpression , nameof ( parameterExpression ) ) ;
var expression = AccessProperty ( parameterExpression , properties ) ;
return Expression . Lambda < Func < TParam , TReturn > > ( expression , parameterExpression ) ;
}
#endregion Build not Nullable delegate
#region Build Nullable delegate
public static LambdaExpression BuildAccessNullablePropertyLambdaExpression ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
{
return BuildAccessNullablePropertyLambdaExpression ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static LambdaExpression BuildAccessNullablePropertyLambdaExpression ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
var parameterExpression = Expression . Parameter ( type ) ;
var expression = AccessNullableProperty ( parameterExpression , properties ) ;
return Expression . Lambda ( expression , parameterExpression ) ;
}
public static Expression < Func < TParam , TReturn > > BuildAccessNullablePropertyLambdaExpression < TParam , TReturn > ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
{
return BuildAccessNullablePropertyLambdaExpression < TParam , TReturn > ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static Expression < Func < TParam , TReturn > > BuildAccessNullablePropertyLambdaExpression < TParam , TReturn > ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
var parameterExpression = Expression . Parameter ( type ) ;
var expression = AccessNullableProperty ( parameterExpression , properties ) ;
return Expression . Lambda < Func < TParam , TReturn > > ( expression , parameterExpression ) ;
}
public static Expression < Func < TParam , TReturn > > BuildAccessNullablePropertyLambdaExpression < TParam , TReturn > (
[NotNull] this ParameterExpression parameterExpression ,
[NotNull] string [ ] properties )
{
ArgumentNotNull ( parameterExpression , nameof ( parameterExpression ) ) ;
var expression = AccessNullableProperty ( parameterExpression , properties ) ;
return Expression . Lambda < Func < TParam , TReturn > > ( expression , parameterExpression ) ;
}
#endregion Build Nullable delegate
#region Extension method
public static Delegate ToDelegate ( [ NotNull ] this Expression expression )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
return ToLambdaExpression ( expression ) . Compile ( ) ;
}
public static LambdaExpression ToLambdaExpression ( [ NotNull ] this Expression expression )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
return Expression . Lambda ( expression , GetRootParameterExpression ( expression ) ) ;
}
public static Expression < Func < TItem , TProp > > ToFuncExpression < TItem , TProp > ( [ NotNull ] this Expression expression )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
return Expression . Lambda < Func < TItem , TProp > > ( expression , GetRootParameterExpression ( expression ) ) ;
}
#endregion Extension method
#region DefaultValue handle
public static Expression AccessPropertyDefaultIfNull < TValue > ( [ NotNull ] this Type type , [ NotNull ] string properties , [ NotNull ] string separator , TValue defaultValue )
{
return AccessPropertyDefaultIfNull ( type , properties . Split ( separator ) , defaultValue ) ;
}
public static Expression AccessPropertyDefaultIfNull < TValue > ( [ NotNull ] this Type type , [ NotNull ] string properties , TValue defaultValue )
{
return AccessPropertyDefaultIfNull ( type , properties . Split ( DefaultPathSeparator ) , defaultValue ) ;
}
public static Expression AccessPropertyDefaultIfNull < TValue > ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties , TValue defaultValue )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
var valueType = typeof ( TValue ) ;
var propertyExp = AccessNullableProperty ( type , properties ) ; // will get Nullable<T> or class
if ( propertyExp . Type . IsValueType )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
// Nullable Value Type
var defaultValueUnderlyingType = Nullable . GetUnderlyingType ( valueType ) ;
var defaultValueTypeIsNullable = defaultValueUnderlyingType ! = null ;
if ( defaultValueTypeIsNullable )
{
var propertyUnderlyingType = Nullable . GetUnderlyingType ( propertyExp . Type ) ;
if ( propertyUnderlyingType ! = defaultValueUnderlyingType )
{
throw new InvalidOperationException ( $"default value type doesn't match the property type: property type '{propertyUnderlyingType?.Name}', default value type '{defaultValueUnderlyingType?.Name}'" ) ;
}
}
if ( propertyExp is UnaryExpression ue )
{
var test = Expression . IsTrue ( Expression . Property ( ue , Nullable_HasValue ) ) ;
Expression trueResult = defaultValueTypeIsNullable ? ue : Expression . Property ( ue , Nullable_Value ) ;
var falseResult = Expression . Constant ( defaultValue , valueType ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
}
else if ( propertyExp is ConditionalExpression ce )
{
var test = Expression . IsTrue ( Expression . Property ( ce , Nullable_HasValue ) ) ;
var trueResult = defaultValueTypeIsNullable ? ce . IfTrue : Expression . Property ( ce . IfTrue , Nullable_Value ) ;
var falseResult = defaultValueTypeIsNullable ? ce . IfFalse : Expression . Constant ( defaultValue , valueType ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
}
else if ( propertyExp is MemberExpression me )
{
var test = Expression . IsTrue ( Expression . Property ( me , Nullable_HasValue ) ) ;
Expression trueResult = defaultValueTypeIsNullable ? me : Expression . Property ( me , Nullable_Value ) ;
var falseResult = Expression . Constant ( defaultValue , valueType ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
}
else
{
throw new NotImplementedException ( $"Unexpected expression type {propertyExp.GetType().Name}" ) ;
}
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
else
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
// Class
var defaultValueType = typeof ( TValue ) ;
var propertyType = propertyExp . Type ;
if ( defaultValueType ! = propertyType )
{
throw new InvalidOperationException ( $"default value type doesn't match the property type: property type '{propertyType?.Name}', default value type '{defaultValueType.Name}'" ) ;
}
var test = Expression . NotEqual ( propertyExp , Expression . Constant ( null , propertyType ) ) ;
var trueResult = propertyExp ;
var falseResult = Expression . Constant ( defaultValue , defaultValueType ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
}
#endregion DefaultValue handle
#region Access nullable property
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
public static Expression AccessNullableProperty ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
{
return AccessNullableProperty ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
public static Expression AccessNullableProperty ( [ NotNull ] this Type type , [ NotNull ] string propertyPath , [ NotNull ] string separator )
{
return AccessNullableProperty ( type , propertyPath . Split ( separator ) ) ;
}
public static Expression AccessNullableProperty ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
var paramExp = Expression . Parameter ( type ) ;
return AccessNullableProperty ( paramExp , properties ) ;
}
public static Expression AccessNullableProperty ( [ NotNull ] this Expression parameterExpression , [ NotNull ] string propertyPath )
{
return AccessNullableProperty ( parameterExpression , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static Expression AccessNullableProperty ( [ NotNull ] this Expression parameterExpression , [ NotNull ] string propertyPath , [ NotNull ] string separator )
{
return AccessNullableProperty ( parameterExpression , propertyPath . Split ( separator ) ) ;
}
public static Expression AccessNullableProperty ( [ NotNull ] this Expression parameterExpression , [ NotNull ] string [ ] properties )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( parameterExpression , nameof ( parameterExpression ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
Expression access = parameterExpression ;
foreach ( var property in properties )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
var indexAccess = ParseIndexAccess ( property ) ;
access = indexAccess . HasValue
? AccessIndex ( access , indexAccess . Value . propertyName , indexAccess . Value . indexes )
: AccessNext ( access , property ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
static Expression AccessIndex ( Expression member , string propertyName , Expression [ ] indexes )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
switch ( propertyName )
{
case { Length : 0 } :
{
foreach ( var index in indexes )
{
member = member . IndexableGetNullableItem ( index ) ;
}
return member ;
}
default :
{
member = AccessNext ( member , propertyName ) ;
foreach ( var index in indexes )
{
member = member . IndexableGetNullableItem ( index ) ;
}
return member ;
}
}
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
static Expression AccessNext ( Expression member , string property )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
if ( member . Type . IsValueType )
{
if ( Nullable . GetUnderlyingType ( member . Type ) = = null )
{
// Value Type
return member . ValueTypeGetProperty ( property ) ;
}
else
{
// Nullable Value Type
return member . NullableTypeGetPropOrNull ( property ) ;
}
}
else
{
// Class
return member . ClassGetPropertyOrNull ( property ) ;
}
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
return TryConvertToNullable ( access ) ;
}
#endregion Access Nullable property
#region Access not null property
public static Expression AccessProperty ( [ NotNull ] this Type type , [ NotNull ] string propertyPath )
{
return AccessProperty ( type , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static Expression AccessProperty ( [ NotNull ] this Type type , [ NotNull ] string propertyPath , [ NotNull ] string separator )
{
return AccessProperty ( type , propertyPath . Split ( separator ) ) ;
}
public static Expression AccessProperty ( [ NotNull ] this Type type , [ NotNull ] string [ ] properties )
{
ArgumentNotNull ( type , nameof ( type ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
var paramExp = Expression . Parameter ( type ) ;
return AccessProperty ( paramExp , properties ) ;
}
public static Expression AccessProperty ( [ NotNull ] this Expression parameterExpression , [ NotNull ] string propertyPath )
{
return AccessProperty ( parameterExpression , propertyPath . Split ( DefaultPathSeparator ) ) ;
}
public static Expression AccessProperty ( [ NotNull ] this Expression parameterExpression , [ NotNull ] string propertyPath , [ NotNull ] string separator )
{
return AccessProperty ( parameterExpression , propertyPath . Split ( separator ) ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
public static Expression AccessProperty ( this Expression parameterExpression , string [ ] properties )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( parameterExpression , nameof ( parameterExpression ) ) ;
ArgumentNotEmpty ( properties , nameof ( properties ) ) ;
Expression access = parameterExpression ;
foreach ( var property in properties )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
var indexAccess = ParseIndexAccess ( property ) ;
if ( ! indexAccess . HasValue )
{
access = AccessNext ( access , property ) ;
}
else
{
access = AccessIndex ( access , indexAccess . Value . propertyName , indexAccess . Value . indexes ) ;
}
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
static Expression AccessIndex ( Expression member , string propertyName , Expression [ ] indexes )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
switch ( propertyName )
{
case { Length : 0 } :
{
return indexes . Aggregate ( member , ( current , index ) = > current . IndexableGetItem ( index ) ) ;
}
default :
{
member = AccessNext ( member , propertyName ) ;
return indexes . Aggregate ( member , ( current , index ) = > current . IndexableGetItem ( index ) ) ;
}
}
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
static Expression AccessNext ( Expression member , string property )
{
// Not index access
if ( member . Type . IsValueType )
{
return Nullable . GetUnderlyingType ( member . Type ) = = null
? member . ValueTypeGetProperty ( property ) // Not Nullable
: member . NullableTypeGetProperty ( property ) ; // Is Nullable
}
else
{
// Class
return member . ClassGetProperty ( property ) ;
}
}
return access ;
}
#endregion Access Not Null Property
//
// Branch
// 1. class : C?
// 2. not null value type : V
// 3. nullable value type : N?
//
// C!.Prop
// C?.Prop
//
// V.Prop
//
// N!.Value
// N!.Value.Prop
// N?.Value
//
#region Property Access
/// <summary>
/// C.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <exception cref="InvalidOperationException"></exception>
private static MemberExpression ClassGetProperty ( this Expression expression , string property )
{
IsClass ( expression ) ;
if ( ! expression . Type . IsClass )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
throw new InvalidOperationException ( $"{nameof(expression)} {expression.Type.Name} must be class" ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
var exp = Expression . Property ( expression , property ) ; // C.Prop
return exp ;
}
/// <summary>
/// C?.Prop
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static ConditionalExpression ClassGetPropertyOrNull ( [ NotNull ] this Expression expression , string property )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
IsClass ( expression ) ;
var test = Expression . NotEqual ( expression , Expression . Constant ( null , expression . Type ) ) ; // E: C == null
var propExp = Expression . Property ( expression , property ) ; // C.Prop
var propIsValueType = propExp . Type . IsValueType & & Nullable . GetUnderlyingType ( propExp . Type ) = = null ;
Expression trueResult = propIsValueType
? Expression . Convert ( propExp , typeof ( Nullable < > ) . MakeGenericType ( propExp . Type ) ) // T: Prop is VT: (Nullable<Prop>)C.Prop
: propExp ; // T: Prop is class: C.Prop
var falseResult = Expression . Constant ( null , trueResult . Type ) ; // F: null
var exp = Expression . Condition ( test , trueResult , falseResult ) ; // E ? T : F;
return exp ;
2021-01-20 23:35:57 +08:00
}
/// <summary>
2021-01-31 01:25:25 +08:00
/// V.Prop
2021-01-20 23:35:57 +08:00
/// </summary>
2021-01-31 01:25:25 +08:00
/// <param name="expression"></param>
/// <param name="property"></param>
2021-01-20 23:35:57 +08:00
/// <returns></returns>
2021-01-31 01:25:25 +08:00
private static MemberExpression ValueTypeGetProperty ( [ NotNull ] this Expression expression , string property )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
IsValueType ( expression ) ;
var exp = Expression . Property ( expression , property ) ; // V.Prop
return exp ;
2021-01-20 23:35:57 +08:00
}
/// <summary>
2021-01-31 01:25:25 +08:00
/// NV?.Value
2021-01-20 23:35:57 +08:00
/// </summary>
2021-01-31 01:25:25 +08:00
/// <param name="expression"></param>
2021-01-20 23:35:57 +08:00
/// <returns></returns>
2021-01-31 01:25:25 +08:00
private static ConditionalExpression NullableTypeGetValueOrNull ( [ NotNull ] this Expression expression )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
IsNullableTypeOrThrow ( expression ) ;
var test = Expression . IsTrue ( Expression . Property ( expression , Nullable_HasValue ) ) ; // E: NV.HasValue == true
var trueResult = Expression . Convert ( Expression . Property ( expression , Nullable_Value ) , expression . Type ) ; // T: (Nullable<T>)NV.Value
var falseResult = Expression . Constant ( null , expression . Type ) ; // F: (Nullable<T>)null
var exp = Expression . Condition ( test , trueResult , falseResult ) ; // E ? T : F
return exp ;
2021-01-20 23:35:57 +08:00
}
/// <summary>
2021-01-31 01:25:25 +08:00
/// NV!.Value, maybe InvalidOperationException for no value
2021-01-20 23:35:57 +08:00
/// </summary>
2021-01-31 01:25:25 +08:00
/// <param name="expression"></param>
2021-01-20 23:35:57 +08:00
/// <returns></returns>
2021-01-31 01:25:25 +08:00
private static MemberExpression NullableTypeGetValue ( [ NotNull ] this Expression expression )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
IsNullableTypeOrThrow ( expression ) ;
var exp = Expression . Property ( expression , nameof ( Nullable < bool > . Value ) ) ; // NV!.Value
return exp ;
2021-01-20 23:35:57 +08:00
}
/// <summary>
2021-01-31 01:25:25 +08:00
/// NV?.Value.Prop
2021-01-20 23:35:57 +08:00
/// </summary>
2021-01-31 01:25:25 +08:00
/// <param name="expression"></param>
/// <param name="property"></param>
2021-01-20 23:35:57 +08:00
/// <returns></returns>
2021-01-31 01:25:25 +08:00
private static ConditionalExpression NullableTypeGetPropOrNull ( [ NotNull ] this Expression expression , string property )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
IsNullableTypeOrThrow ( expression ) ;
var valueExp = NullableTypeGetValueOrNull ( expression ) ; // NV?.Value
var test = Expression . NotEqual ( valueExp , Expression . Constant ( null , valueExp . Type ) ) ; // E: NV?.Value != null
var propExp = NullableTypeGetProperty ( valueExp . IfTrue , property ) ; // Expression.Property(Expression.Property(valueExp.IfTrue, VTValue), property); // NV!.Value.Prop
var propIsValueType = propExp . Type . IsValueType & & Nullable . GetUnderlyingType ( propExp . Type ) = = null ;
Expression trueResult = propIsValueType
? Expression . Convert ( propExp , typeof ( Nullable < > ) . MakeGenericType ( propExp . Type ) ) // T: Prop is VT: (Nullable<Prop>)NV!.Value.Prop
: propExp ; // T: Prop is class: NV!.Value.Prop
var falseResult = Expression . Constant ( null , trueResult . Type ) ; // F: (Nullable<Prop>)null
var exp = Expression . Condition ( test , trueResult , falseResult ) ; // NV?.Value != null ? NV!.Value.Prop : null
return exp ;
}
/// <summary>
/// NV!.Value.Prop, maybe InvalidOperationException for no value
/// </summary>
/// <param name="expression"></param>
/// <param name="property"></param>
/// <returns></returns>
private static MemberExpression NullableTypeGetProperty ( [ NotNull ] this Expression expression , string property )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
IsNullableTypeOrThrow ( expression ) ;
var nvValue = expression . NullableTypeGetValue ( ) ; // NV!.Value
var exp = Expression . Property ( nvValue , property ) ; // NV!.Value.Prop
return exp ;
}
private static Expression IndexableGetItem ( [ NotNull ] this Expression expression , Expression index )
{
var getItemMethod = expression . Type . GetMethod ( GetItemMethodName , BindingFlags . Public | BindingFlags . Instance ) ;
if ( getItemMethod ! = null )
{
var getItemMethodCall = Expression . Call ( expression , getItemMethod , index ) ;
return getItemMethodCall ;
}
else if ( expression . Type . IsArray )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
var indexAccess = Expression . ArrayIndex ( expression , index ) ;
return indexAccess ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
throw new InvalidOperationException ( $"Not supported type '{expression.Type.Name}' for index access" ) ;
}
private static Expression IndexableGetNullableItem ( [ NotNull ] this Expression expression , [ NotNull ] Expression index )
{
// Array
if ( expression . Type . IsArray )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
if ( index . Type ! = typeof ( int ) )
{
throw new InvalidOperationException ( $"Array must be indexed by 'int', but get '{expression.Type.Name}'" ) ;
}
var lengthAccess = Expression . ArrayLength ( expression ) ;
var test = Expression . AndAlso ( Expression . LessThan ( index , lengthAccess ) , Expression . GreaterThanOrEqual ( index , Expression . Constant ( 0 , typeof ( int ) ) ) ) ;
var trueResult = Expression . ArrayIndex ( expression , index ) ;
var falseResult = Expression . Constant ( null , trueResult . Type ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
// Dictionary<,> like, types that have the 'ContainsKey' method and the 'get_Item' method.
{
var getItemMethod = expression . Type . GetMethod ( GetItemMethodName , BindingFlags . Public | BindingFlags . Instance ) ;
var containsKeyMethod = expression . Type . GetMethod ( nameof ( IDictionary < int , int > . ContainsKey ) , BindingFlags . Public | BindingFlags . Instance ) ;
if ( getItemMethod ! = null & & containsKeyMethod ! = null )
{
var containsKeyCall = Expression . Call ( expression , containsKeyMethod , index ) ;
var test = Expression . IsTrue ( containsKeyCall ) ;
var trueResult = Expression . Call ( expression , getItemMethod , index ) ;
var falseResult = Expression . Constant ( null , trueResult . Type ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
}
}
// Lisk like, types that have the 'get_Item' method and the 'Count' property.
{
var getItemMethod = expression . Type . GetMethod ( GetItemMethodName , BindingFlags . Public | BindingFlags . Instance ) ;
var countProperty = expression . Type . GetProperty ( CountPropertyName , BindingFlags . Public | BindingFlags . Instance ) ;
if ( getItemMethod ! = null & & countProperty ! = null )
{
if ( index . Type ! = typeof ( int ) )
{
throw new InvalidOperationException ( $"{expression.Type.Name} must be indexable by 'int', but get '{index.Type.Name}'" ) ;
}
var countAccess = Expression . Property ( expression , countProperty ) ;
var test = Expression . AndAlso ( Expression . LessThan ( index , countAccess ) , Expression . GreaterThanOrEqual ( index , Expression . Constant ( 0 , typeof ( int ) ) ) ) ;
var trueResult = Expression . Call ( expression , getItemMethod , index ) ;
var falseResult = Expression . Constant ( null , trueResult . Type ) ;
return Expression . Condition ( test , trueResult , falseResult ) ;
}
}
throw new InvalidOperationException ( $"Not supported type '{expression.Type.Name}' for index accessing" ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
#endregion Property Access
#region Type Validate
2021-01-20 23:35:57 +08:00
/// <summary>
2021-01-31 01:25:25 +08:00
/// Check if expression.Type is class, otherwise throw and exception
2021-01-20 23:35:57 +08:00
/// </summary>
2021-01-31 01:25:25 +08:00
/// <param name="expression"></param>
2021-01-20 23:35:57 +08:00
/// <exception cref="ArgumentNullException"></exception>
2021-01-31 01:25:25 +08:00
/// <exception cref="InvalidOperationException"></exception>
private static void IsClass ( [ NotNull ] Expression expression )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
if ( ! expression . Type . IsClass )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
throw new InvalidOperationException ( $"{nameof(expression)} {expression.Type.Name} must be class" ) ;
2021-01-20 23:35:57 +08:00
}
}
2021-01-31 01:25:25 +08:00
/// <summary>
/// Check if expression.Type is ValueType and not Nullable<T>, otherwise throw and exception
/// </summary>
/// <param name="expression"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static void IsValueType ( [ NotNull ] Expression expression )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
ArgumentNotNull ( expression , nameof ( expression ) ) ;
if ( expression . Type . IsValueType & & Nullable . GetUnderlyingType ( expression . Type ) ! = null )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
throw new InvalidOperationException ( $"{nameof(expression)} {expression.Type.Name} must be value type" ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
}
/// <summary>
/// Check if expression.Type is Nullable<T>, otherwise throw and exception
/// </summary>
/// <param name="expression"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private static void IsNullableTypeOrThrow ( [ NotNull ] Expression expression )
{
ArgumentNotNull ( expression , nameof ( expression ) ) ;
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
if ( Nullable . GetUnderlyingType ( expression . Type ) = = null )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
throw new InvalidOperationException ( $"{nameof(expression)} {expression.Type.Name} must be typeof Nullable<T>" ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
}
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
#endregion Type Validate
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
#region Utils
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
private static void ArgumentNotNull < T > ( in T arg , string argName )
where T : class
{
if ( arg = = default )
2021-01-20 23:35:57 +08:00
{
2021-01-31 01:25:25 +08:00
throw new ArgumentNullException ( argName ) ;
2021-01-20 23:35:57 +08:00
}
2021-01-31 01:25:25 +08:00
}
2021-01-20 23:35:57 +08:00
2021-01-31 01:25:25 +08:00
private static void ArgumentNotEmpty < T > ( in T [ ] arg , string argName )
where T : class
{
if ( arg = = default | | arg is { Length : 0 } )
{
throw new ArgumentException ( "Value cannot be an empty collection or null." , argName ) ;
}
2021-01-20 23:35:57 +08:00
}
/// <summary>
/// expression should be like: ParameterExpression->MemberExpression1->MemberExpression2... ,
2021-01-31 01:25:25 +08:00
/// if the root for 'expression' is not ParameterExpression, this will return null.
2021-01-20 23:35:57 +08:00
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
2021-01-31 01:25:25 +08:00
private static ParameterExpression GetRootParameterExpression ( [ NotNull ] Expression expression )
2021-01-20 23:35:57 +08:00
{
return expression switch
{
MemberExpression { Expression : { } } memberExp = > GetRootParameterExpression ( memberExp . Expression ) ,
2021-01-31 01:25:25 +08:00
ConditionalExpression conditionalExp = > GetRootParameterExpression ( conditionalExp . IfTrue ) ,
UnaryExpression unaryExp = > GetRootParameterExpression ( unaryExp . Operand ) ,
2021-01-20 23:35:57 +08:00
ParameterExpression paramExp = > paramExp ,
2021-01-31 01:25:25 +08:00
_ = > throw new NullReferenceException ( )
2021-01-20 23:35:57 +08:00
} ;
}
2021-01-31 01:25:25 +08:00
/// <summary>
/// Try convert Expression type to Nullable type, only Non-Nullable ValueType can be converted
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
private static Expression TryConvertToNullable ( [ NotNull ] Expression expression )
{
if ( expression . Type . IsValueType & & Nullable . GetUnderlyingType ( expression . Type ) = = null )
{
return Expression . Convert ( expression , typeof ( Nullable < > ) . MakeGenericType ( expression . Type ) ) ;
}
return expression ;
}
/// <summary>
/// Check if property string has index operation and parse to Expression
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private static ( string propertyName , Expression [ ] indexes ) ? ParseIndexAccess ( string property )
{
const string IndexAccessErrorTemplate = "Invalid index property: {0}, index must be like 'prop[key]' or 'prop[key][key]...'" ;
if ( ! property . EndsWith ( ']' ) )
{
return null ;
}
property = property . Replace ( ' ' , '\0' ) ;
var sp = property . AsSpan ( ) ;
var begin = 0 ;
var end = sp . IndexOf ( '[' ) ;
if ( end = = - 1 )
{
throw new InvalidOperationException ( string . Format ( IndexAccessErrorTemplate , property ) ) ;
}
var propertyName = sp . Slice ( begin , end ) . ToString ( ) ; // from 0 to index of first '[' treat as property name
sp = sp . Slice ( end ) ;
var leftCount = 0 ;
var rightCount = 0 ;
for ( int i = 0 ; i < sp . Length ; i + + )
{
if ( sp [ i ] = = '[' )
{
leftCount + + ;
}
else if ( sp [ i ] = = ']' )
{
rightCount + + ;
}
}
if ( leftCount ! = rightCount )
{
throw new InvalidOperationException ( string . Format ( IndexAccessErrorTemplate , property ) ) ;
}
var indexes = new Expression [ leftCount ] ;
var count = 0 ;
while ( sp . Length ! = 0 )
{
var indexBegin = sp . IndexOf ( '[' ) ;
var indexEnd = sp . IndexOf ( ']' ) ;
if ( indexBegin = = - 1 | | indexEnd = = - 1 | | indexBegin + 1 = = indexEnd ) // ']' not found or found "[]" which is incorrect
{
throw new InvalidOperationException ( ) ;
}
var idxSpan = sp . Slice ( indexBegin + 1 , indexEnd - indexBegin - 1 ) ;
Expression indexExp ;
if ( idxSpan [ 0 ] = = '"' & & idxSpan [ ^ 1 ] = = '"' )
{
// is string key
var key = idxSpan . Slice ( 1 , idxSpan . Length - 2 ) . ToString ( ) ;
indexExp = Expression . Constant ( key , typeof ( string ) ) ;
}
else
{
// is int key
if ( ! int . TryParse ( idxSpan , out var indexNum ) )
{
throw new InvalidOperationException ( string . Format ( IndexAccessErrorTemplate , property ) ) ;
}
indexExp = Expression . Constant ( indexNum , typeof ( int ) ) ;
}
indexes [ count ] = indexExp ;
count + + ;
sp = sp . Slice ( indexEnd + 1 ) ;
}
return ( propertyName , indexes ) ;
}
#endregion Utils
2021-01-20 23:35:57 +08:00
}
}