何时应该在C#中使用LINQ?

34

我正在学习C#,而且我发现LINQ非常有趣。然而,让我困惑的是,我想不出一个场景,在这种情况下使用LINQ会帮助我解决问题,因为在代码中复制LINQ的功能真的不难。

你有任何个人经验或建议想要分享吗?

谢谢!


有一个投票以“主观和争议性”为理由关闭。那太愚蠢了!这只是关于Linq的独特价值的简单问题。(另一方面,它肯定是SO上重复提问的问题,因此可能因此而被关闭。) - Daniel Earwicker
我愿意打赌它们是“维基或接近”的投票。这是主观的,所以它可能应该是一个维基问题。 - Greg
7
我正在学习 C#,发现 foreach 很有趣。但困扰我的是,我无法想象在什么情况下使用 foreach 会极大地帮助我,因为在代码中复制 foreach 的功能并不难。 - user7116
2
谷歌搜索 LINQ 将显示出数量惊人的结果,其中包含大量的示例。 SO 上有90页关于 LINQ 的问题。我不明白这如何为现有信息的大量增添任何东西。应该关闭或转为 Wiki。 - user7116
在“为什么使用LINQ”http://stackoverflow.com/search?q=why+use+linq中搜索,会出现https://dev59.com/OnRC5IYBdhLWcg3wUfN2等多个相关问题。 - dmckee --- ex-moderator kitten
显示剩余4条评论
15个回答

20

我发现每当我需要填充容器时,我就会使用LINQ,而不是以前常用的循环。我在ORM方面使用LINQ to SQL,在其他方面也广泛使用LINQ。

这是我为Active Directory助手类编写的一个小片段,它可以查找特定用户是否在特定组中。请注意使用Any()方法迭代用户的授权组,直到找到一个具有匹配SID的组。与替代方案相比,代码更加简洁。

private bool IsInGroup( GroupPrincipal group, UserPrincipal user )
{
    if (group == null || group.Sid == null)
    {
        return false;
    }
    return user.GetAuthorizationGroups()
               .Any( g => g.Sid != null && g.Sid.CompareTo( group.Sid ) == 0 );
}

替代方案:

private bool IsInGroup( GroupPrincipal group, UserPrincipal user )
{
    if (group == null || group.Sid == null)
    {
        return false;
    }
    bool inGroup = false;
    foreach (var g in user.GetAuthorizationGroups())
    {
         if ( g => g.Sid != null && g.Sid.CompareTo( group.Sid ) == 0 )
         {
            inGroup = true;
            break;
         }
    }
    return inGroup;
}
或者
private bool IsInGroup( GroupPrincipal group, UserPrincipal user )
{
    if (group == null || group.Sid == null)
    {
        return false;
    }

    foreach (var g in user.GetAuthorizationGroups())
    {
         if ( g => g.Sid != null && g.Sid.CompareTo( group.Sid ) == 0 )
         {
            return true;
         }
    }
    return false;
}

以下是一段代码片段,它对存储库进行搜索,并将前10个匹配的业务对象按顺序转换为视图特定的模型(Distance 是匹配模型的唯一id与uniqueID参数之间的Levenshtein编辑距离)。

model.Results = this.Repository.FindGuestByUniqueID( uniqueID, withExpired )
                               .OrderBy( g => g.Distance )
                               .Take( 10 )
                               .ToList()
                               .Select( g => new GuestGridModel( g ) );

2
有趣的话题,关于循环/填充容器的详细说明?一些代码示例? - nubela
严格来说,这并不是使用LINQ,而是使用LINQ语法转换为的扩展方法。我只是发现流畅的风格更容易阅读 - 在与jQuery来回切换时减少了认知失调。当然,您也可以使用LINQ语法表示上述所有内容。 - tvanfosson
1
嗯,如果它在System.Linq命名空间中,我认为无论用哪种语法调用它,都算是使用LINQ。 - Joel Mueller
1
在你的第二个例子中,一旦找到一个用户,为什么要继续迭代用户?你可以直接返回true; - Idan K
@ldan K - 是的,我可以简单地返回true,但我更喜欢重用函数中的最终退出点。这只是个人偏好(而且我并不总是一致的,注意如果组或用户为空,我会提前返回)。我不会继续迭代,但请注意,在将inGroup设置为true后,我立即跳出循环。我会提供另一种选择,虽然它比使用LINQ更简单,但仍然不够紧凑。 - tvanfosson

16

这取决于您指的是哪种类型的linq。

如果是linq-to-sql,那么它是一个ORM,具有与使用任何其他ORM相同的所有优点。我很少使用它,不能说更多。

如果是linq-to-objects,那么您实际上在谈论其他东西的集合:扩展方法,惰性迭代器和查询理解语法。这是介绍函数式编程世界的一种方式。我对查询理解语法没有太多用处,但对于其他部分,我可以通过以下示例进行最佳演示。

假设您想要逐行读取文件。对于每一行,您都想检查它是否符合某些条件,将其中某些行的一部分转换为整数,并将前10个在某个范围内的这些整数求和。这里是旧的方法:

int SumXValues(string filename)
{
    string line;
    int sum = 0;
    int count = 0;
    using (var rdr = new StreamReader(filename))
    {

        while ( (line = rdr.ReadLine()) != null && count < 10)
        {
           int val;
           if (int.TryParse(line.Substring(3,3))
           {
               if (val > 4 && val < 25)
               {
                    sum += val;
                    count ++;
               }
            }
        }
    }
    return sum;
}

这是新的方法:

IEnumerable<string> ReadLines(string filename)
{
    string line;
    using (var rdr = new StreamReader(filename))
        while ( (line = rdr.ReadLine()) != null)
           yield return line;
}

int SumXValues(string filename)
{
    return ReadLines(filename)
               .Select(l => l.Substring(3,3))
               .Where(l => int.TryParse(l))
               .Select(i => int.Parse(i))
               .Where(i => i > 4 && i < 16)
               .Take(10)
               .Sum(i => i);
}

注意新的代码实际上更短。但为什么它也更 好 呢?至少有4个原因:

  • 希望大家能够清楚地看出readlines函数是多么可重用。你也可以将更多内容分解,但关键是展示这种风格如何帮助您重用更多的代码。
  • 它的扩展性更好。请注意最后一个函数中所有链接的函数调用。您知道该代码将在文件的哪些行上迭代多少次吗?恰好一次!事实上,它甚至不会很长,因为它将在获取前10个项目之后停止从文件中读取。如果您将其更改为返回可枚举并在其他扩展方法中使用它呢?仍然只有一次!这使您能够在运行时构建、混合和重新混合查询结果,而无需对列表进行昂贵的额外操作。
  • 它更易于维护。如果标准发生变化,很容易找到您关心的确切“规则”,并修改那部分。
  • 它更易读。这使您能够以执行操作的方式表达代码,而不是以如何执行操作的方式表达。

+!很好的例子,充分证明了理由。 - Yvo

12

当我有一个对象集合,并且我需要查找符合某些条件的项时,我发现使用LINQ非常有用。简单的例子是,在一组形状中搜索所有圆形的形状。

var circles =
        from s in allShapes
        where s.Type == ShapeTypes.Circle
        select s;

我承认我本可以写一个带有一些代码的循环,但我发现这段代码更短、更容易编写,更容易阅读和理解。

LINQ也可以用于数据库,但我实际上并不经常使用它。我想每个人都有自己的偏好吧。


11

这本书 Essential LINQ 提供了一个很好的段落概述LINQ的好处:

LINQ不仅仅是为语言增加新特性。它将一种声明式编程风格引入到C#语言中。声明式编程模型允许开发人员编写能够简洁地捕捉其意图的代码,而无需担心事件发生的顺序或其精确实现。它允许开发人员陈述他们想要做什么,而不是如何去做。


7

你说得对,使用常规C#很难复制这些功能。这就是所谓的语法糖,只是为了方便。你知道自动属性吗?它们允许你编写 public class User { public int Id { get; set; } } 这在“常规”C#代码中非常容易复制。即使如此,自动属性仍然很棒 ;)

过滤和排序几乎是LINQ的核心。假设你有一个用户列表,称为users,你想找到今天登录的用户,并按最近登录的顺序显示它们。在使用LINQ之前,你可能会创建一个新列表,根据原始列表进行迭代,添加符合条件的内容,然后实现某种IComparable。现在你可以这样做:

users = users.Where(u => u.LastLoggedIn.Date = DateTime.Today)
             .OrderBy(u => u.LastLoggedIn).ToList();

方便 =)


1
为什么要调用.ToList()呢?十有八九是不需要的,而且会影响性能。 - Joel Coehoorn
我同意这一点,但我的例子假设有一个用户列表,就像前面的文本所述。不过,我很容易改变我的例子 =) - David Hedlund

4

我经常使用LINQ to DataSet来处理DataTable。DataTable是一种通用的数据存储方式,我经常将从加载的CSV文件中获取的值存储在其中。使用LINQ进行查询或连接数据比使用"brute-force"-ing循环更易读和舒适。


3
我想反过来问:你能展示一下如何在没有Linq的情况下模拟Linq的功能吗?我很难想到一个不使用Linq就可以轻松完成的例子。
例如,最近我看到了这样的代码:
foreach (var person in people.OrderBy(p => p.Company)
                             .ThenBy(p => p.LastName)
                             .ThenBy(p => p.FirstName)) {
    ...
}

我想可以使用 Arrays.Sort,创建一个委托来按正确的顺序检查字段(从写入的反向开始,对吗?),然后只接受它只能在数组上工作的事实。这似乎会更加冗长,难以维护和不太灵活。


2
是的,您可以轻松使用替代代码来使用LINQ to Objects,而且并不难。我倾向于喜欢Lambda表达式的语义,它可以将几行代码包装成一行。但是许多操作都可以使用自己的代码完成,只有一些较大的操作(如联合、交集等)更容易使用LINQ完成。
LINQ还有其他变体;LINQ to XML使处理XML数据变得非常方便。我真的比以前可用的对象更喜欢它。
LINQ to SQL和ADO.NET Entity Framework(带有LINQ to Entities)是对象关系映射器,可以映射到您的数据库表,并像存储过程和ADO.NET数据集一样工作,因此这是一个非常好的替代选择,而不是弱数据集/数据表,我也喜欢它超过强类型数据集/表。
希望对您有所帮助。

因为提到了交集/并集,所以点赞。这也是我发现LINQ最有益处的地方 :)除此之外,它通常比foreach循环更好地记录意图。 - Merlyn Morgan-Graham

2

1

有人提到LINQ是一种声明式编程风格,我想进一步扩展一下。

我使用LINQ的一种方式是编写测试用例代码。测试代码必须简单明了,并且尽可能接近“显然正确”,否则它将会出现与被测试代码一样多的错误。对于我正在测试的一个特定功能,我编写了一小组集合推导式,准确地描述了该功能的工作方式。由于LINQ的存在,将这些推导式转换为代码变得非常容易:

A = all items
B = [x in A: x.Type = selectedtype, x.Source = "sourceA"]
C = [x in A: x.Source = "sourceB"]
D = B union C

在代码中:

IEnumerable<MyClass> SetB(IEnumerable<MyClass> allItems, MyType type)
{
  var result = from item in allItems
               where item.Type == type && item.Source == "sourceA"
               select item;
  return result;
}

IEnumerable<MyClass> SetC(IEnumerable<MyClass> allItems)
{
  var result = from item in allItems
               where item.Source == "sourceB"
               select item;
  return result;
}

IEnumerable<MyClass> SetD(IEnumerable<MyClass> allItems, MyType type)
{
  var setB = SetB(allItems, type);
  var setC = SetC(allItems);
  return setB.Union(setC);
}

虽然比数学表达式冗长得多,但与命令式代码相比,它更简单、更容易被称为“显然正确”。LINQ 代码像数学一样是声明性的。翻译较少,更接近规范。当适当使用时,LINQ 几乎是一种“做我想要的”语言。

请注意,我不会以这种方式编写实际代码。性能对于测试代码来说并不是必需的,但对于真正的代码来说却是必需的,因此真正的代码仍然需要是 SQL 存储过程。


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