在C#中从通用对象获取属性

3
请看这段代码:
public void BindElements<T>(IEnumerable<T> dataObjects)
{
    Paragraph para = new Paragraph();

    foreach (T item in dataObjects)
    {
        InlineUIContainer uiContainer =
            this.CreateElementContainer(item.FirstName ????? )              
        para.Inlines.Add(uiContainer);
    }                         

    FlowDocument flowDoc = new FlowDocument(para);
    this.Document = flowDoc;
}

当我在Visual Studio中写“item.XXX”时,我应该从我的实体中获取属性,例如.FirstName或.LastName。我不知道dataObjects是IEnumerable还是IOrder等等...它必须是通用的!

我如何才能获得item的真正属性?只能使用反射吗?


我认为这比public void BindElements(object dataObjects)更好,因为那样我就不必进行强制转换了... - Elisabeth
4个回答

8

Oded is right,他认为尝试将此方法泛型化似乎没有任何意义。您正在尝试使功能实际上特定于少数类型的方法泛型化。

话虽如此,似乎这个函数的大部分与您想要访问的属性无关。那么为什么不将其拆分为两个部分:可以泛型化的部分和不可泛型化的部分:

像这样:

void BindElements<T, TProperty>(IEnumerable<T> dataObjects,
                                Func<T, TProperty> selector)
{
    Paragraph para = new Paragraph();

    foreach (T item in dataObjects)
    {
       // Notice: by delegating the only type-specific aspect of this method
       // (the property) to (fittingly enough) a delegate, we are able to 
       // package MOST of the code in a reusable form.
       var property = selector(item);

       InlineUIContainer uiContainer = this.CreateElementContainer(property)
       para.Inlines.Add(uiContainer);
    }

    FlowDocument flowDoc = new FlowDocument(para);
    this.Document = flowDoc;
}

然后你可以处理特定类型的重载,例如IPerson,可以重用这段代码(我猜这可能是你一直在追求的—代码重用):

public void BindPeople(IEnumerable<IPerson> people)
{
    BindElements(people, p => p.FirstName);
}

接下来是关于IOrder的内容:

public void BindOrders(IEnumerable<IOrder> orders)
{
    BindElements(orders, o => p.OrderNumber);
}

...以此类推。


啊,我以为我已经评论过了,好吧,现在我来评论:@Dan Tao => 如果我想传递两个这样的“选择器”,比如FirstName和LastName,那么BindElements方法的参数会如何改变?我从未见过/读过TProperty + selector,也不太理解它;-)刚开始做真正的通用东西。 - Elisabeth
+1 - 绝对是一个令人满意的实现,而且符合我的思路,其中Func可以是一种工厂方法,用于创建她的元素容器。 - bitxwise

4
如果您向通用类型添加约束条件(比如它必须实现IPerson接口),则可以使用在接口上定义的任何方法:
public void BindElements<T>(IEnumerable<T> dataObjects) where T : IPerson

如果IPerson定义了FirstNameLastName属性,您可以使用它们与T一起使用。
查看链接以了解可能的通用约束不同类型。

赶我抢答。我的答案几乎完全相同。 - siride
@Oded 抱歉让你感到困惑,它不应该意味着...是一个IEnumerable,而应该意味着ICustomer或IOrder等等。但你已经接近理解了...问题是我不能把IPerson放在那里,因为那会破坏该方法的通用功能!公开BindElements方法的UserControl的用户正在传递List<T>或IEnumerable<T>,而T可以是IPerson、ICustomer或IOrder,你明白吗? - Elisabeth
2
@Lisa - 但是,想要在通用参数上调用“特定”方法,您已经“破坏”了函数的通用性质。请解释为什么多个重载不比串改的泛型函数更好? - Oded
@Oded她的泛型函数即使使用反射,如果不是所有实体都有FirstName属性,也无法正常工作。 - siride
@bitxwise 和 siride => 没有我可以使用的基类。这个 UserControl 必须与现有应用程序一起工作,同时又独立于此应用程序... @bitxwise 你说得对,我只是添加了那些容器,并从项目中获取 DisplayMember 属性而已! - Elisabeth
显示剩余5条评论

3

补充Dan的答案,Func<T, TProperty> selector 只是表示 selector 是一个方法的标识符,该方法接受类型为 T 的参数,并具有返回类型 TProperty。因此,作为第二个参数传递到 BindElements 中的有效方法可以是,例如,

string CreatePersonElement(IPerson person) {
    return string.Format("{0} {1}", person.FirstName, person.LastName);
}

在这种情况下,TProperty会被定义为一个string类型,而T将会是IPerson类型。然后你可以像以下这样调用BindElements函数。
BindElements(myPersonCollection,CreatePersonElement);

其中 myPersonCollection 可以是您所指的任何 List<T>。然后继续进行 foreach 循环。

foreach (T item in dataObjects) {
   // Notice: by delegating the only type-specific aspect of this method
   // (the property) to (fittingly enough) a delegate, we are able to 
   // package MOST of the code in a reusable form.
   var property = selector(item);

   InlineUIContainer uiContainer = this.CreateElementContainer(property)
   para.Inlines.Add(uiContainer);
}

property属性被设置为TProperty类型的对象,对于CreatePersonElement来说,它是一个string。如果string不适用于您的情况,请将方法的返回类型更改为CreateElementContainer接受其作为参数的任何内容。

然后,您需要为每种要支持的类型(即ICustomerIOrder)编写一个这样的方法,并将其传递给第二个参数BindElements


不是解决方案的位运算,但值得一个大点: P感谢示例! - Elisabeth
不客气 - 是的,丹绝对值得为这个解决方案获得赞誉 =) - bitxwise

0

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