mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-12-05 13:37:35 +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 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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