refactor(module: select): use Func to get or set value instead of reflection (#1168)

* refactor(module: select): use Func to get or set value instead of reflection

* refactor(module: select): add delegate cache

* chore: add DelegateCacheKeyComparer

Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Zonciu Liang 2021-02-28 00:13:13 +08:00 committed by GitHub
parent 4065869806
commit 0f6702b5ed
2 changed files with 302 additions and 28 deletions

View File

@ -38,10 +38,32 @@ namespace AntDesign
} }
} }
[Parameter] public bool Disabled { get; set; } [Parameter] public bool Disabled { get; set; }
[Parameter] public string DisabledName { get; set; }
[Parameter]
public string DisabledName
{
get => _disabledName;
set
{
_getDisabled = string.IsNullOrWhiteSpace(value) ? null : SelectItemPropertyHelper.CreateGetDisabledFunc<TItem>(value);
_disabledName = value;
}
}
[Parameter] public Func<RenderFragment, RenderFragment> DropdownRender { get; set; } [Parameter] public Func<RenderFragment, RenderFragment> DropdownRender { get; set; }
[Parameter] public bool EnableSearch { get; set; } [Parameter] public bool EnableSearch { get; set; }
[Parameter] public string GroupName { get; set; } = string.Empty;
[Parameter]
public string GroupName
{
get => _groupName;
set
{
_getGroup = string.IsNullOrWhiteSpace(value) ? null : SelectItemPropertyHelper.CreateGetGroupFunc<TItem>(value);
_groupName = value;
}
}
[Parameter] public bool HideSelected { get; set; } [Parameter] public bool HideSelected { get; set; }
[Parameter] public bool IgnoreItemChanges { get; set; } = true; [Parameter] public bool IgnoreItemChanges { get; set; } = true;
[Parameter] public RenderFragment<TItem> ItemTemplate { get; set; } [Parameter] public RenderFragment<TItem> ItemTemplate { get; set; }
@ -51,7 +73,18 @@ namespace AntDesign
/// </summary> /// </summary>
[Parameter] public bool LabelInValue { get; set; } [Parameter] public bool LabelInValue { get; set; }
[Parameter] public string LabelName { get; set; } [Parameter]
public string LabelName
{
get => _labelName;
set
{
_getLabel = SelectItemPropertyHelper.CreateGetLabelFunc<TItem>(value);
_setLabel = SelectItemPropertyHelper.CreateSetLabelFunc<TItem>(value);
_labelName = value;
}
}
[Parameter] public RenderFragment<TItem> LabelTemplate { get; set; } [Parameter] public RenderFragment<TItem> LabelTemplate { get; set; }
[Parameter] public bool Loading { get; set; } [Parameter] public bool Loading { get; set; }
[Parameter] public string Mode { get; set; } = "default"; [Parameter] public string Mode { get; set; } = "default";
@ -86,7 +119,19 @@ namespace AntDesign
[Parameter] public RenderFragment PrefixIcon { get; set; } [Parameter] public RenderFragment PrefixIcon { get; set; }
[Parameter] public char[] TokenSeparators { get; set; } [Parameter] public char[] TokenSeparators { get; set; }
[Parameter] public override EventCallback<TItemValue> ValueChanged { get; set; } [Parameter] public override EventCallback<TItemValue> ValueChanged { get; set; }
[Parameter] public string ValueName { get; set; }
[Parameter]
public string ValueName
{
get => _valueName;
set
{
_getValue = SelectItemPropertyHelper.CreateGetValueFunc<TItem, TItemValue>(value);
_setValue = SelectItemPropertyHelper.CreateSetValueFunc<TItem, TItemValue>(value);
_valueName = value;
}
}
[Parameter] public EventCallback<IEnumerable<TItemValue>> ValuesChanged { get; set; } [Parameter] public EventCallback<IEnumerable<TItemValue>> ValuesChanged { get; set; }
/// <summary> /// <summary>
@ -340,7 +385,28 @@ namespace AntDesign
} }
} }
private string _labelName;
private Func<TItem, string> _getLabel;
private Action<TItem, string> _setLabel;
private string _groupName = string.Empty;
private Func<TItem, string> _getGroup;
private string _disabledName;
private Func<TItem, bool> _getDisabled;
private string _valueName;
private Func<TItem, TItemValue> _getValue;
private Action<TItem, TItemValue> _setValue;
#endregion Properties #endregion Properties
private static bool IsSimpleType(Type type) private static bool IsSimpleType(Type type)
{ {
return return
@ -462,7 +528,7 @@ namespace AntDesign
foreach (var item in _datasource) foreach (var item in _datasource)
{ {
TItemValue value = GetPropertyValueAsTItemValue(item, ValueName); TItemValue value = _getValue(item);
var exists = false; var exists = false;
SelectOptionItem<TItemValue, TItem> selectOption; SelectOptionItem<TItemValue, TItem> selectOption;
@ -481,13 +547,13 @@ namespace AntDesign
var disabled = false; var disabled = false;
var groupName = string.Empty; var groupName = string.Empty;
var label = GetPropertyValueAsObject(item, LabelName)?.ToString(); var label = _getLabel(item);
if (!string.IsNullOrWhiteSpace(DisabledName)) if (!string.IsNullOrWhiteSpace(DisabledName))
disabled = (bool)GetPropertyValueAsObject(item, DisabledName); disabled = _getDisabled(item);
if (!string.IsNullOrWhiteSpace(GroupName)) if (!string.IsNullOrWhiteSpace(GroupName))
groupName = GetPropertyValueAsObject(item, GroupName)?.ToString(); groupName = _getGroup(item);
if (!exists) if (!exists)
{ {
@ -1001,8 +1067,8 @@ namespace AntDesign
else else
{ {
item = Activator.CreateInstance<TItem>(); item = Activator.CreateInstance<TItem>();
typeof(TItem).GetProperty(LabelName).SetValue(item, _searchValue); _setLabel(item, _searchValue);
typeof(TItem).GetProperty(ValueName).SetValue(item, value); _setValue(item, value);
} }
return new SelectOptionItem<TItemValue, TItem>() { Label = label, Value = value, Item = item, IsActive = isActive, IsSelected = false, IsAddedTag = true }; return new SelectOptionItem<TItemValue, TItem>() { Label = label, Value = value, Item = item, IsActive = isActive, IsSelected = false, IsAddedTag = true };
} }
@ -1086,22 +1152,6 @@ namespace AntDesign
await _dropDown.GetOverlayComponent().UpdatePosition(); await _dropDown.GetOverlayComponent().UpdatePosition();
} }
private static object GetPropertyValueAsObject(object obj, string propertyName)
{
return obj.GetType().GetProperties()
.Single(p => p.Name == propertyName)
.GetValue(obj, null);
}
private static TItemValue GetPropertyValueAsTItemValue(object obj, string propertyName)
{
var result = obj.GetType().GetProperties()
.Single(p => p.Name == propertyName)
.GetValue(obj, null);
return (TItemValue)TypeDescriptor.GetConverter(typeof(TItemValue)).ConvertFromInvariantString(result.ToString());
}
#region Events #region Events
/// <summary> /// <summary>
@ -1398,8 +1448,8 @@ namespace AntDesign
} }
else else
{ {
typeof(TItem).GetProperty(LabelName).SetValue(CustomTagSelectOptionItem.Item, _searchValue); _setLabel(CustomTagSelectOptionItem.Item, _searchValue);
typeof(TItem).GetProperty(ValueName).SetValue(CustomTagSelectOptionItem.Item, value); _setValue(CustomTagSelectOptionItem.Item, value);
} }
} }
} }

View File

@ -0,0 +1,224 @@
// 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace AntDesign.Select.Internal
{
internal static class SelectItemPropertyHelper
{
private const string ToStringMethodName = "ToString";
private const string Nullable_HasValue = "HasValue";
private const string Nullable_Value = "Value";
private static readonly ConcurrentDictionary<DelegateCacheKey, Delegate> _getValueDelegateCache = new(new DelegateCacheKeyComparer());
private static readonly ConcurrentDictionary<DelegateCacheKey, Delegate> _setValuedDelegateCache = new(new DelegateCacheKeyComparer());
internal static Func<TItem, TItemValue> CreateGetValueFunc<TItem, TItemValue>(string valueName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(TItemValue), valueName);
var func = _getValueDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var labelExp = Expression.Property(itemParamExp, valueName);
var labelPropType = (labelExp.Member as PropertyInfo)?.PropertyType ?? throw new InvalidOperationException($"Member '{valueName}' should be Property");
if (labelPropType.IsClass)
{
var funExp = Expression.Lambda<Func<TItem, TItemValue>>(labelExp, itemParamExp);
return funExp.Compile();
}
var labelIsNullable = labelPropType.IsValueType && Nullable.GetUnderlyingType(labelPropType) != null;
if (labelIsNullable)
{
var test = Expression.IsTrue(Expression.Property(labelExp, Nullable_HasValue));
var trueResult = Expression.Convert(Expression.Property(labelExp, Nullable_Value), labelExp.Type);
var falseResult = Expression.Constant(null, labelExp.Type);
var resultExp = Expression.Condition(test, trueResult, falseResult);
var funExp = Expression.Lambda<Func<TItem, TItemValue>>(resultExp, itemParamExp);
return funExp.Compile();
}
else
{
var valueIsNullable = typeof(TItemValue).IsValueType && Nullable.GetUnderlyingType(typeof(TItemValue)) != null;
Expression valueExp = valueIsNullable ? Expression.New(typeof(Nullable<>).MakeGenericType(labelExp.Type).GetConstructors()[0], labelExp) : labelExp;
var funExp = Expression.Lambda<Func<TItem, TItemValue>>(valueExp, itemParamExp);
return funExp.Compile();
}
});
return (Func<TItem, TItemValue>)func;
}
internal static Func<TItem, string> CreateGetLabelFunc<TItem>(string labelName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(string), labelName);
var func = _getValueDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var labelExp = Expression.Property(itemParamExp, labelName);
var labelPropType = (labelExp.Member as PropertyInfo)?.PropertyType ?? throw new InvalidOperationException($"Member '{labelName}' should be Property");
if (labelPropType == typeof(string))
{
var funExp = Expression.Lambda<Func<TItem, string>>(labelExp, itemParamExp);
return funExp.Compile();
}
else
{
var resultExp = Expression.Call(labelExp, labelPropType.GetMethod(ToStringMethodName) ?? throw new MissingMethodException(labelPropType.Name, ToStringMethodName));
var funExp = Expression.Lambda<Func<TItem, string>>(resultExp, itemParamExp);
return funExp.Compile();
}
});
return (Func<TItem, string>)func;
}
internal static Func<TItem, string> CreateGetGroupFunc<TItem>(string groupName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(string), groupName);
var func = _getValueDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var groupExp = Expression.Property(itemParamExp, groupName);
var groupPropType = (groupExp.Member as PropertyInfo)?.PropertyType ?? throw new InvalidOperationException($"Member '{groupName}' should be Property");
if (groupPropType == typeof(string))
{
var funExp = Expression.Lambda<Func<TItem, string>>(groupExp, itemParamExp);
return funExp.Compile();
}
else
{
var resultExp = Expression.Call(groupExp, groupPropType.GetMethod(ToStringMethodName) ?? throw new MissingMethodException(groupPropType.Name, ToStringMethodName));
var funExp = Expression.Lambda<Func<TItem, string>>(resultExp, itemParamExp);
return funExp.Compile();
}
});
return (Func<TItem, string>)func;
}
internal static Func<TItem, bool> CreateGetDisabledFunc<TItem>(string disabledName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(bool), disabledName);
var func = _getValueDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var disabledExp = Expression.Property(itemParamExp, disabledName);
var disabledPropType = (disabledExp.Member as PropertyInfo)?.PropertyType ?? throw new InvalidOperationException($"Member '{disabledName}' should be Property");
if (disabledPropType == typeof(bool))
{
var funExp = Expression.Lambda<Func<TItem, bool>>(disabledExp, itemParamExp);
return funExp.Compile();
}
else
{
throw new InvalidOperationException($"Member '{disabledName}' should be type of 'bool'");
}
});
return (Func<TItem, bool>)func;
}
internal static Action<TItem, string> CreateSetLabelFunc<TItem>(string labelName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(string), labelName);
var action = _setValuedDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var labelParamExp = Expression.Parameter(typeof(string));
var labelExp = Expression.Property(itemParamExp, labelName);
var setLabelExp = Expression.Assign(labelExp, labelParamExp);
var funExp = Expression.Lambda<Action<TItem, string>>(setLabelExp, itemParamExp, labelParamExp);
return funExp.Compile();
});
return (Action<TItem, string>)action;
}
internal static Action<TItem, TItemValue> CreateSetValueFunc<TItem, TItemValue>(string valueName)
{
var key = new DelegateCacheKey(typeof(TItem), typeof(TItemValue), valueName);
var action = _setValuedDelegateCache.GetOrAdd(
key,
_ =>
{
var itemParamExp = Expression.Parameter(typeof(TItem));
var valueParamExp = Expression.Parameter(typeof(TItemValue));
var valueExp = Expression.Property(itemParamExp, valueName);
var valuePropType = (valueExp.Member as PropertyInfo)?.PropertyType ?? throw new InvalidOperationException($"Member '{valueName}' should be Property");
if (valuePropType.IsClass)
{
var setLabelExp = Expression.Assign(valueExp, valueParamExp);
var funExp = Expression.Lambda<Action<TItem, TItemValue>>(setLabelExp, itemParamExp, valueParamExp);
return funExp.Compile();
}
var valueIsNullable = valuePropType.IsValueType && Nullable.GetUnderlyingType(valuePropType) != null;
var valueParamIsNullable = valueParamExp.Type.IsValueType && Nullable.GetUnderlyingType(valueParamExp.Type) != null;
if (valueIsNullable)
{
Expression nullableValueExp = valueParamIsNullable
? valueParamExp
: Expression.New(valuePropType.GetConstructors()[0], valueParamExp);
var setLabelExp = Expression.Assign(valueExp, nullableValueExp);
var funExp = Expression.Lambda<Action<TItem, TItemValue>>(setLabelExp, itemParamExp, valueParamExp);
return funExp.Compile();
}
else
{
var setLabelExp = valueParamIsNullable
? Expression.Assign(valueExp, Expression.Property(valueParamExp, Nullable_Value))
: Expression.Assign(valueExp, valueParamExp);
var funExp = Expression.Lambda<Action<TItem, TItemValue>>(setLabelExp, itemParamExp, valueParamExp);
return funExp.Compile();
}
});
return (Action<TItem, TItemValue>)action;
}
private readonly struct DelegateCacheKey
{
public readonly Type ItemType;
public readonly Type ValueType;
public readonly string PropertyName;
public DelegateCacheKey(Type itemType, Type valueType, string propertyName)
{
ItemType = itemType;
ValueType = valueType;
PropertyName = propertyName;
}
}
private class DelegateCacheKeyComparer : IEqualityComparer<DelegateCacheKey>
{
public bool Equals(DelegateCacheKey x, DelegateCacheKey y)
{
return x.ItemType == y.ItemType
&& x.ValueType == y.ValueType
&& x.PropertyName == y.PropertyName;
}
public int GetHashCode(DelegateCacheKey obj)
{
return HashCode.Combine(obj.ItemType, obj.ValueType, obj.PropertyName);
}
}
}
}