LINQ 语句在包装到扩展方法中后不再起作用

发布于 2024-09-14 08:44:37 字数 939 浏览 6 评论 0原文

我需要一种可以获取字符串集合并用另一个字符串替换所有出现的特定字符串的方法。

例如,如果我有一个看起来像这样的 List

List<string> strings = new List<string> { "a", "b", "delete", "c", "d", "delete" };

并且我想将“delete”替换为“”,我会使用这个 LINQ 语句:

strings = (from s in strings select (s=="delete" ? s=String.Empty : s)).ToList();

它效果很好。但后来我想我应该将其作为扩展方法,因为我以后可能会再次使用它。在这种情况下,我只想编写以下内容:

strings.ReplaceStringInListWithAnother( "delete", String.Empty);

当我的代码编译并且 LINQ 语句在扩展方法内部工作时,当我返回集合时,它会恢复到其原始内容:

public static void ReplaceStringInListWithAnother( this List<string> my_list, string to_replace, string replace_with)
{
    my_list = (from s in my_list select (s==to_replace ? s=replace_with : s)).ToList();
}

所以看起来我只是修改了一个副本列表的...但是当我查看 Pop 的代码时,它类似地修改了集合,但更改仍然存在,所以我的假设是我的方法的参数声明是正确的。

谁能解释我在这里做错了什么?

I had a need for a method that could take a collection of strings, and replace all occurrences of a specific string with another.

For example, if I have a List<string> that looks like this:

List<string> strings = new List<string> { "a", "b", "delete", "c", "d", "delete" };

and I want to replace "delete" with "", I would use this LINQ statement:

strings = (from s in strings select (s=="delete" ? s=String.Empty : s)).ToList();

and it works great. But then I figured I should make it an extension method, since I'd likely use it again later. In this case, I just want to write the following:

strings.ReplaceStringInListWithAnother( "delete", String.Empty);

While my code compiles, and the LINQ statement works inside of the extension method, when I return the collection reverts back to its original contents:

public static void ReplaceStringInListWithAnother( this List<string> my_list, string to_replace, string replace_with)
{
    my_list = (from s in my_list select (s==to_replace ? s=replace_with : s)).ToList();
}

So it would seem that I just modified a copy of the List... but when I looked at the code for Pop, it modifies the collection similarly, yet the changes stick, so my assumption was that my method's parameter declarations are correct.

Can anyone explain what I am doing wrong here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ぇ气 2024-09-21 08:44:38

这里有一个提示:您希望下面的代码做什么?

void SetToTen(int y)
{
    y = 10;
}

int x = 0;
SetToTen(x);

希望您明白上面的 SetToTen 方法没有任何意义,因为它仅更改其自己的局部变量 y 的值,并且对传递值的变量没有影响(为了实现这一点,y 参数必须是 ref int 类型,并且该方法将被调用为 SetToTen(ref x) )。

请记住,扩展方法实际上只是华丽服装中的静态方法,应该清楚为什么您的 ReplaceStringInListWithAnother 没有执行您期望的操作:它只是设置其本地 < code>my_list 变量为新值,对传递给该方法的原始 List 没有影响。

现在,值得一提的是,这对您不起作用的唯一原因是您的代码通过将变量设置为新对象*来工作。如果您要修改传递给ReplaceStringInListWithAnotherList,一切都会正常工作:

public static void ReplaceStringInListWithAnother( this List<string> my_list, string to_replace, string replace_with)
{
    for (int i = 0; i < my_list.Count; ++i)
    {
        if (my_list[i] == to_replace)
        {
            my_list[i] = replace_with;
        }
    }
}

也是值得一提的是 List 对于该方法来说是一个过于严格的参数类型;您可以为任何实现 IList 的类型实现相同的功能(因此我将 my_list 参数更改为 IList< 类型/代码>)。


*再次阅读您的问题,我似乎清楚这是您感到困惑的主要点。您必须意识到的重要一点是,默认情况下,C# 中的所有内容都是按值传递的。对于值类型(定义为 struct 的任何内容 - intdoubleDateTime 等等), 传递的东西是值本身。对于引用类型(定义为的任何内容),传递的内容对对象的引用。在后一种情况下,对可变类型对象的引用的所有方法调用实际上都会影响底层对象,因为引用类型的多个变量可以指向同一个对象。但赋值与方法调用不同;如果您将一个引用分配给一个已通过值传递给某个对象的新引用的对象,则您不会对底层对象执行任何操作,因此不会发生原始引用所反映的任何事情。

这是一个非常重要的概念,许多 .NET 开发人员都在努力解决这个问题。但这也是一个在其他地方已经得到彻底解释的话题。如果您需要更多解释,请告诉我,我将尝试找到一个页面链接,使所有这些内容尽可能清晰。

Here's a hint: what do you expect the below code to do?

void SetToTen(int y)
{
    y = 10;
}

int x = 0;
SetToTen(x);

Hopefully, you understand that the SetToTen method above does nothing meaningful, since it only changes the value of its own local variable y and has no effect on the variable whose value was passed to it (in order for that to happen, the y parameter would have to be of type ref int and the method would be called as SetToTen(ref x)).

Keeping in mind that extension methods are really just static methods in fancy clothes, it should be clear why your ReplaceStringInListWithAnother is not doing what you expected: it is only setting its local my_list variable to a new value, having no effect on the original List<string> passed to the method.

Now, it's worth mentioning that the only reason this is not working for you is that your code works by setting a variable to a new object*. If you were to modify the List<string> passed to ReplaceStringInListWithAnother, everything would work just fine:

public static void ReplaceStringInListWithAnother( this List<string> my_list, string to_replace, string replace_with)
{
    for (int i = 0; i < my_list.Count; ++i)
    {
        if (my_list[i] == to_replace)
        {
            my_list[i] = replace_with;
        }
    }
}

It's also worth mentioning that List<string> is an overly restrictive parameter type for this method; you could achieve the same functionality for any type implementing IList<string> (and so I'd change the my_list parameter to be of type IList<string>).


*Reading your question again, it seems clear to me that this is the main point of confusion for you. The important thing you have to realize is that by default, everything in C# is passed by value. With value types (anything defined as a struct -- int, double, DateTime, and many more), the thing that's passed is the value itself. With reference types (anything that's defined as a class), the thing that's passed is a reference to an object. In the latter case, all method calls on references to objects of mutable types do actually affect the underlying object, since multiple variables of reference type can point to the same object. But assignment is different from a method call; if you assign a reference to an object that has been passed by value to some new reference to an object, you are doing nothing to the underlying object, and therefore nothing is happening that would be reflected by the original reference.

This is a really important concept that many .NET developers struggle with. But it's also a topic that's been explained thoroughly elsewhere. If you need more explanation, let me know and I'll try to dig up a link to a page that makes all of this as clear as possible.

你在我安 2024-09-21 08:44:38

您还没有显示“Pop”的代码,因此很难知道您的意思。您谈论“当我返回集合时”,但您没有返回任何内容 - 该方法具有 void 返回类型。

LINQ 通常不会更改现有集合的内容。通常,您应该从扩展方法返回一个集合。例如:(

public static IEnumerable<string> ReplaceAll
    (this IEnumerable<string> myList, string toReplace, string replaceWith)
{
    return toReplace.Select(x => x == toReplace ? replaceWith : x);
}

我在这里使它更通用 - 除非确实需要,否则不应该开始具体化列表。)

然后您可以使用以下方式调用它:

strings = strings.ReplaceAll("delete", "").ToList();

... 或更改 stringIEnumerable 并使用

strings = strings.ReplaceAll("delete", "");

You haven't shown the code for "Pop" so it's hard to know what you mean. You talk about "when I return the collection" but you're not returning anything - the method has a void return type.

LINQ typically doesn't change the contents of an existing collection. Usually you should return a new collection from the extension method. For example:

public static IEnumerable<string> ReplaceAll
    (this IEnumerable<string> myList, string toReplace, string replaceWith)
{
    return toReplace.Select(x => x == toReplace ? replaceWith : x);
}

(I've made it more general here - you shouldn't start materializing lists unless you really need to.)

You'd then call it with:

strings = strings.ReplaceAll("delete", "").ToList();

... or change the type of string to IEnumerable<string> and just use

strings = strings.ReplaceAll("delete", "");
甜警司 2024-09-21 08:44:37

您编写的 LINQ 语句不会修改集合,它实际上创建一个新集合。

您编写的扩展方法创建了这个新集合,然后丢弃它。该分配是多余的:您正在分配一个本地参数,该参数在分配后立即超出范围。

当您调用该方法时,您还会丢弃其结果而不是将其分配回来。

因此,你应该这样编写方法:

public static List<string> ReplaceStringInListWithAnother(
    this List<string> my_list, string to_replace, string replace_with)
{
    return (from s in my_list select
        (s == to_replace ? replace_with : s)).ToList();
}

和这样的调用:

strings = strings.ReplaceStringInListWithAnother("delete", "");

顺便说一句,你可以通过使其通用来使函数更有用:

public static List<T> ReplaceInList<T>(this List<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    return (from s in my_list select
        (s.Equals(to_replace) ? replace_with : s)).ToList();
}

这样你就可以将它用于其他用途,而不仅仅是 strings.此外,您还可以声明它使用 IEnumerable 而不是 List

public static IEnumerable<T> ReplaceItems<T>(this IEnumerable<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    return from s in my_list select (s.Equals(to_replace) ? replace_with : s);
}

这样您就可以将它用于任何可相等项的集合,而不仅仅是 <代码>列表。请注意,List 实现了 IEnumerable,因此您仍然可以将 List 传递到此函数中。如果您想要输出一个列表,只需在调用此列表后调用 .ToList() 即可。

更新:如果您确实想要替换列表中的元素而不是创建新元素,您仍然可以使用扩展方法来实现这一点,并且它仍然可以是通用的,但你不能使用 Linq,也不能使用 IEnumerable

public static void ReplaceInList<T>(this List<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    for (int i = 0; i < my_list.Count; i++)
        if (my_list[i].Equals(to_replace))
            my_list[i] = replace_with;
}

这不会返回新列表,而是修改旧列表,因此它有一个 void 返回类型与原始类型相同。

The LINQ statement you wrote does not modify the collection, it actually creates a new one.

The extension method you wrote creates this new collection and then discards it. The assignment is redundant: you’re assigning to a local parameter, which goes out of scope immediately after.

When you’re calling the method, you’re also discarding its result instead of assigning it back.

Therefore, you should write the method like this:

public static List<string> ReplaceStringInListWithAnother(
    this List<string> my_list, string to_replace, string replace_with)
{
    return (from s in my_list select
        (s == to_replace ? replace_with : s)).ToList();
}

and the call like this:

strings = strings.ReplaceStringInListWithAnother("delete", "");

By the way, you can make the function more useful by making it generic:

public static List<T> ReplaceInList<T>(this List<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    return (from s in my_list select
        (s.Equals(to_replace) ? replace_with : s)).ToList();
}

This way you can use it for other things, not just strings. Furthermore, you can also declare it to use IEnumerable<T> instead of List<T>:

public static IEnumerable<T> ReplaceItems<T>(this IEnumerable<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    return from s in my_list select (s.Equals(to_replace) ? replace_with : s);
}

This way you can use it for any collection of equatable items, not just List<T>. Notice that List<T> implements IEnumerable<T>, so you can still pass a List into this function. If you want a list out, simply call .ToList() after the call to this one.

Update: If you actually want to replace elements in a list instead of creating a new one, you can still do that with an extension method, and it can still be generic, but you can’t use Linq and you can’t use IEnumerable<T>:

public static void ReplaceInList<T>(this List<T> my_list,
    T to_replace, T replace_with) where T : IEquatable<T>
{
    for (int i = 0; i < my_list.Count; i++)
        if (my_list[i].Equals(to_replace))
            my_list[i] = replace_with;
}

This will not return the new list, but instead modify the old one, so it has a void return type like your original.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文