mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-12-04 21:17:36 +08:00
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:
parent
4065869806
commit
0f6702b5ed
@ -38,10 +38,32 @@ namespace AntDesign
|
||||
}
|
||||
}
|
||||
[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 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 IgnoreItemChanges { get; set; } = true;
|
||||
[Parameter] public RenderFragment<TItem> ItemTemplate { get; set; }
|
||||
@ -51,7 +73,18 @@ namespace AntDesign
|
||||
/// </summary>
|
||||
[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 bool Loading { get; set; }
|
||||
[Parameter] public string Mode { get; set; } = "default";
|
||||
@ -86,7 +119,19 @@ namespace AntDesign
|
||||
[Parameter] public RenderFragment PrefixIcon { get; set; }
|
||||
[Parameter] public char[] TokenSeparators { 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; }
|
||||
|
||||
/// <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
|
||||
|
||||
private static bool IsSimpleType(Type type)
|
||||
{
|
||||
return
|
||||
@ -462,7 +528,7 @@ namespace AntDesign
|
||||
|
||||
foreach (var item in _datasource)
|
||||
{
|
||||
TItemValue value = GetPropertyValueAsTItemValue(item, ValueName);
|
||||
TItemValue value = _getValue(item);
|
||||
|
||||
var exists = false;
|
||||
SelectOptionItem<TItemValue, TItem> selectOption;
|
||||
@ -481,13 +547,13 @@ namespace AntDesign
|
||||
|
||||
var disabled = false;
|
||||
var groupName = string.Empty;
|
||||
var label = GetPropertyValueAsObject(item, LabelName)?.ToString();
|
||||
var label = _getLabel(item);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisabledName))
|
||||
disabled = (bool)GetPropertyValueAsObject(item, DisabledName);
|
||||
disabled = _getDisabled(item);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(GroupName))
|
||||
groupName = GetPropertyValueAsObject(item, GroupName)?.ToString();
|
||||
groupName = _getGroup(item);
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
@ -1001,8 +1067,8 @@ namespace AntDesign
|
||||
else
|
||||
{
|
||||
item = Activator.CreateInstance<TItem>();
|
||||
typeof(TItem).GetProperty(LabelName).SetValue(item, _searchValue);
|
||||
typeof(TItem).GetProperty(ValueName).SetValue(item, value);
|
||||
_setLabel(item, _searchValue);
|
||||
_setValue(item, value);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
@ -1398,8 +1448,8 @@ namespace AntDesign
|
||||
}
|
||||
else
|
||||
{
|
||||
typeof(TItem).GetProperty(LabelName).SetValue(CustomTagSelectOptionItem.Item, _searchValue);
|
||||
typeof(TItem).GetProperty(ValueName).SetValue(CustomTagSelectOptionItem.Item, value);
|
||||
_setLabel(CustomTagSelectOptionItem.Item, _searchValue);
|
||||
_setValue(CustomTagSelectOptionItem.Item, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
224
components/select/internal/SelectItemPropertyHelper.cs
Normal file
224
components/select/internal/SelectItemPropertyHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user