使用反射在ViewModel中创建通用类型命令的缺点有哪些?

3

我有一个数据对象,它有许多不同的List属性。我想使用单个AddObject命令,而不是为每个List创建一个不同的命令,因此想出了以下代码。

您看到使用这种方法的任何缺点吗?我认为性能可能会变慢,但是实际上我没有看到差异。

public MyViewModel()
{ 
    _addCommand = new RelayCommand<IEnumerable>(AddGenericObject);

    // Old code.... defines an Add command per list
    // _addAddressCommand = new RelayCommand(() => AddObject<Address>(AddressList));
    // _addPhoneCommand = new RelayCommand(() => AddObject<Phone>(PhoneList));
    // ... etc
}

private void AddGenericObject(IEnumerable list)
{
    // Find Add Method
    var addMethod = this.GetType()
        .GetMethod("AddObject", BindingFlags.NonPublic | BindingFlags.Instance);

    // Created Generic Add Method
    Type genericType = list.GetType().GetGenericArguments()[0];
    var genericAddMethod = addMethod.MakeGenericMethod(genericType);

    // Invoke Method
    genericAddMethod.Invoke(this, new object[] { list });
}

private void AddObject<T>(EntityCollection<T> list)
   where T : EntityObject, new()
{
    var newItem = new T();
    list.Add(newItem);
}

它可以在XAML中这样使用:

<Button Content="New Address" 
        Command="{Binding AddCommand}"
        CommandParameter="{Binding AddressList}" />

<Button Content="New Phone" 
        Command="{Binding AddCommand}"
        CommandParameter="{Binding PhoneList}" />

正如下面的评论所建议的那样,反射性能是一个大问题。考虑使用缓存机制来缓存反射结果以加快性能。 - Jethro
在这种情况下还好,但它有点脆弱。当列表不是通用的或包含不同类型的实例时会发生什么?显然,在您自己的代码库中,您可以轻松控制它。太糟糕了ICommand不像事件那样给你源,否则你可以通过附加属性指定目标类型。我喜欢附加属性。它们让我感到快乐,就像小狗的亲吻一样。 - user1228
4个回答

1
一句话概括 - 性能,但在你摆脱代码之前,请对其进行基准测试 - 它可能已经足够快了。

1

在考虑使用反射时,性能始终是最重要的因素。通常情况下,应该尽量避免使用反射。


0

性能是一个重要因素,正如James和Dror所建议的那样。你可以使用以下方法进行缓存。

    private MethodInfo cachedMethod = null;
    private void AddGenericObject(IEnumerable list)
    {
        if (cachedMethod == null)
        {
            // Find Add Method
            var addMethod = this.GetType()
                 .GetMethod("AddObject", BindingFlags.NonPublic | BindingFlags.Instance);

            // Created Generic Add Method
            var genericType = list.GetType().GetGenericArguments()[0];
            cachedMethod = addMethod.MakeGenericMethod(genericType);
        }

        // Invoke Method
        cachedMethod.Invoke(this, new object[] { list });
    }

我认为那不会起作用,因为该方法需要使用许多不同的泛型类型进行调用,而缓存只能保存一个泛型类型方法的定义。 - Rachel
1
因为所有类型都将继承IEnumerable,所以我认为不会有问题。如果我错了,那么你需要做的就是创建一个List<MethodInfo>或Dictionary<T, MethodInfo>,然后你可以检查是否已经缓存了该特定类型。如果您想看一下示例,我有一个地方可以提供给您。 - Jethro

0
我的做法是使用Freezable作为命令对象,将一个类型为Action(of Object)的委托作为DependencyProperty赋给它,并使用一个转换器。该转换器接受一个方法名作为ConverterParameter,并将对集合的引用转换为对集合Add方法(或您在ConverterParameter中使用的任何方法名称)的委托,同时绑定到集合。这样,反射的成本只有在绑定的集合对象本身被交换时才会发生,这应该是相当罕见的,因为您希望保留相同的集合并仅添加和删除项目。
如果您正在寻找示例代码,可以查看此博客文章:http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接