可枚举类型扩展方法

15

我想创建一个 IEnumerable<TSource> 扩展方法,使其能够转换成一个 IEnumerable<SelectListItem>。到目前为止,我一直在尝试这样做:

    public static 
      IEnumerable<SelectListItem> ToSelectItemList<TSource, TKey>(this 
      IEnumerable<TSource> enumerable, Func<TSource, TKey> text, 
                                       Func<TSource, TKey> value)
    {
        List<SelectListItem> selectList = new List<SelectListItem>();

        foreach (TSource model in enumerable)
            selectList.Add(new SelectListItem() { Text = ?, Value = ?});

        return selectList;
    }

这是正确的方法吗?如果是,如何从Func<TSource, TKey>的相应值中提取值?


5
你可能还可以考虑放弃 selectList,改用 foreach (...) yield return new SelectListItem { ... },这样可以实现延迟执行(更符合 LINQ 风格)。 - Rawling
1
为什么你不直接使用Select呢? - jk.
我的代码中有很多地方需要调用这个函数,从查看Select函数来看,似乎我必须编写更多的代码才能实现这种方式。使用Select,我需要编写代码来实例化一个SelectListItem,而不仅仅是传递Func<>。此外,使用这种方法,处理转换为SelectListItem的代码被封装并存储在我的代码的一个区域,而不是分散在整个应用程序中。 - Rick Eyre
我认为你真正需要的是一个工厂/构造函数,用于从TSource和两个Func<TSource,TKey>创建一个SelectListItem - 从IEnumerable<Tsource>到IEnumerable<SelectListItem>的投影就是选取。 - jk.
那种方法相比这种方法有什么优势?.Select() 函数是否比这个扩展方法更有效率或者其他方面更好? - Rick Eyre
7个回答

26
你正在重新发明轮子。这正是Enumerable.Select的意图。 @KeithS编辑:回答这个问题,如果你想要这个输出,你可以定义一个包装Enumerable.Select的扩展方法。
public static IEnumerable<SelectListItem> ToSelectItemList<TSource>(
  this IEnumerable<TSource> enumerable,
  Func<TSource, string> text,
  Func<TSource, string> value)
{ 
  return enumerable.Select(x=>new SelectListItem{Text=text(x), Value=value(x));
}

15

你走在正确的道路上。

Funcs是存储在变量中的方法,并像普通方法一样被调用。

public static IEnumerable<SelectListItem> ToSelectItemList<TSource, TKey>(
    this IEnumerable<TSource> enumerable,
    Func<TSource, TKey> text,
    Func<TSource, TKey> value)
{
    List<SelectListItem> selectList = new List<SelectListItem>();

    foreach (TSource model in enumerable)
    {
        selectList.Add(new SelectListItem()
        {
            Text = text(model),
            Value = value(model)
        });
    }

    return selectList;
}
如果我可以建议,你的 Func 应该是 Func<TSource,string>,因为 SelectListItem 中的文本和值都是字符串。 编辑 刚想到这个……
此外,您不必创建内部列表,而可以使用 yield return。以下是我对您方法的“优化”版本。
public static IEnumerable<SelectListItem> ToSelectItemList<TSource>(
    this IEnumerable<TSource> enumerable,
    Func<TSource, string> text,
    Func<TSource, string> value)
{
    foreach (TSource model in enumerable)
    {
        yield return new SelectListItem()
        {
            Text = text(model),
            Value = value(model)
        };
    }
}

以下是yield return的参考资料。它允许你将结果作为可枚举集合(enumerable)的元素返回,同时不会在代码中显式构造该可枚举集合。

http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx


在你的建议中,你是指 Func<TSource, string> 吗? - Richard
我该怎么称呼这个?我还是新手。 - Rick Eyre
1
很酷,yield return 的使用也很好。 - Richard
要调用扩展方法,请在 using 部分引用其命名空间,就像你保留其他的 using 一样。然后,你就可以像调用 Enumerable 的本地方法一样调用该方法了。这个链接应该比我更好地描述了它:http://msdn.microsoft.com/en-us/library/bb311042.aspx - Thinking Sites

15

你只需要使用作为参数提供的两个函数来提取文本和值。假设文本和值都是字符串,你不需要TKey类型参数。而且在扩展方法中创建列表是没有必要的。一个使用yield return的迭代器块更可取,这也是LINQ中类似扩展方法的构建方式。

public static IEnumerable<SelectListItem> ToSelectItemList<TSource>(
  this IEnumerable<TSource> enumerable,
  Func<TSource, string> text,
  Func<TSource, string> value)
{ 
  foreach (TSource model in enumerable) 
    yield return new SelectListItem { Text = text(model), Value = value(model) };
}

您可以像这样使用它(需要提供两个lambda函数):

var selectedItems = items.ToSelecListItem(x => ..., x => ...);

不过,你同样可以使用 Enumerable.Select

var selectedItems = items.Select(x => new SelectListItem { Text = ..., Value = ... });

我偶尔使用类似的东西,不过我添加了3个可选参数(默认为false和2个空字符串),并以if(includeEmptyOption)yield return new SelectListItem {Text = emptyOptionText,Value = emptyOptionValue};开始方法。按照使用的顺序排序参数,这样我就可以指定emptyOptionText并获得默认的emptyOptionValue为“”。 - Chris Shaffer

4
对我来说,这就像过河取水一样。为什么不直接使用select呢?
enumerable.Select(item => 
                    new SelectListItem{
                          Text = item.SomeProperty, 
                           Value item.SomeOtherProperty
                    }).ToList();

如果你真的需要一个方法,那么你可以这样做:

public static 
      IEnumerable<SelectListItem> ToSelectItemList<TSource, TKey>(this 
      IEnumerable<TSource> enumerable, Func<TSource, TKey> text, 
                                       Func<TSource, TKey> value)
    {
        return (from item in enumerable
                select new SelectListItem{
                      Text = text(item),
                      Value = value(item)
                }).ToList();  
    }

4
您想要实现的一种LINQ方式是:

使用LINQ可以实现您想要的功能:

public static IEnumerable<SelectListItem> ToSelectItemList<TSource, TKey>(
    this IEnumerable<TSource> enumerable, 
    Func<TSource, TKey> textSelector, 
    Func<TSource, TKey> valueSelector)
{
    return from model in enumerable
           select new SelectListItem 
           { 
               Text = textSelector(model), 
               Value = valueSelector(model) 
           };
}

感谢您复制我的答案。好的观点值得重复 :) (为了复制功能,您应该将其转换为列表或至少说明为什么您认为那不是一个好主意) - Rune FS
在我看来,我们的答案略有不同,你将其转换为列表,而我没有。将其转换为列表并不是必要的,因为函数声明指定结果为IEnumerable,如果不将其转换为列表,则函数的调用者可以使用其他LINQ操作组合结果,从而短路源的完整枚举(例如.First、.Single等)。 - Patrick McDonald

0

在您的扩展方法的主体内,这两个参数只是委托,您可以像运行任何其他函数一样运行它们:

        selectList.Add(new SelectListItem() { Text = text(model), Value = value(model)});

0

其他解决方案也可以,但我认为Martin Liversage的方法是最好的:

IEnumerable<SelectListItem> selectListItems = items.Select(x => 
    new SelectListItem 
        { 
            Text = x.TextProperty, 
            Value = x.ValueProperty 
        });

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