MoreLINQ: Ease usage in next major version by renaming methods conflicting with Enumerable methods

For historical reasons, some of MoreLinq’s method names conflict with System.Linq.Enumerable’s, causing compiler errors like the following from Issue 527:

The call is ambiguous between the following methods or properties: 'System.Linq.Enumerable.Append(System.Collections.Generic.IEnumerable, TSource)' and 'MoreLinq.MoreEnumerable.Append(System.Collections.Generic.IEnumerable, T)'

This can often be resolved by using a static import of one of MoreLinq’s *Extension classes, such as MoreLinq.Extensions.AppendExtension.

However, having to manually do a static import rather than being able to continue to use tooling’s auto-import (such as provided by Visual Studio or ReSharper), while it makes a work-around possible, is still a big pain and a slow-down. Furthermore it seems that some cases of using methods from both Linq and MoreLinq in the same file are only resolvable by calling the extension method from the static class name directly, rather than using it as an extension of IEnumerable. These problems are said to be rare, but I have ran into it several times just in the past few weeks (perhaps because I do a lot of functional-style C# programming…which is exactly why I love MoreLinq!)

For this reason I propose that in the next major version of MoreLinq, the conflicting method names be renamed where they may cause this conflict. (The same thing could happen again in the future if more Enumerable methods are added to System.Linq, but at least the conflicts will be reduced, and for a while exhaustively.)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 25
  • Comments: 44 (18 by maintainers)

Commits related to this issue

Most upvoted comments

Let me throw my three cents worth into this discussion.

While I understand the approach to keep this library conflict-safe from other unknown amount of libraries in the wild, I think you should stop and think about these totally unproven statistics:

  1. 100% of users unhappy with MoreLinq being conflicted with Append/Prepend/System.Linq are using MoreLinq.
  2. 99.(9)% of those users must surely also be using System.Linq at the same time.
  3. An unknown fraction of this number could be using more LINQ libraries and/or obsolete .NET versions, causing inevitable conflicts.

I see you are trying to make your library safe to use with any and every .NET version and LINQ libraries and while this is honourable, I am unhappy in being in the 99.(9)% group that also must fallback to static imports, which are a nightmare in MoreLinq because:

  1. Once you add your first static import and remove using MoreLinq, Intellisense will stop suggesting available operators.
  2. It ruins the flow of typing queries.
  3. Usually when a conflict occurs in a file, you already have used several MoreLinq operators, so having been faced with necessity to first change them all to static imports before being able to continue does not provoke a smile.
  4. It is not supported in LINQPad, which is the best tool to test LINQ queries, but suddenly MoreLinq is so hard to test I catch myself thinking about library betrayal.

If I may, please let me try to convince you to sacrifice uttermost compatibility with infinite past and future, to instead make your 99.(9)% user group happy and make their life easy again, as it was in MoreLinq 2.x. The fallback to static imports seems a fine solution for the rest of them.

P.S. Maybe - although I have no experience in it and it may be impossible - some conditional compilation targets could be configured, where MoreLinq 3 could be targeting several .NET versions, conflict-free with each thanks to #if directives?

think about these totally unproven statistics

@ensisnoctis So here’s the other side of the coin I’m looking at and where we do have some statistics we can debate on. At the time of writing this, the MoreLINQ package has been downloaded ~3.5M times. Version 3.0, where static imports were introduced, is the second highest downloaded version at over ½M downloads. The only other two versions in the same neighbourhood are versions 2.10 and 1.4 (550,073 and 581,644 downloads, respectively). Given those, the number of people who have come screaming and kicking about this issue are far shy of 10. It tells one of two things about the rest: they are either happy and have accepted the state of affairs as being a fair trade-off or they are all blissfully unaware of the pain and storm that awaits them because they haven’t yet upgraded to a version of .NET where conflicts exist.

please let me try to convince you to sacrifice uttermost compatibility with infinite past and future, to instead make your 99.(9)% user group happy and make their life easy again

So I’m afraid I’m not convinced yet but I haven’t closed this issue precisely for the reason that I am waiting for the hoards of folks that I hear will be coming in with their pitchforks. The issue is right there for anyone to convey their horror though I’d rather see a fruitful discussion with people proposing (thought out and objective) solutions and contributing ideas rather than just express their discontentment.

static imports, which are a nightmare in MoreLinq because:

Let’s talk about your nightmare now.

4. It is not supported in LINQPad, which is the best tool to test LINQ queries, but suddenly MoreLinq is so hard to test I catch myself thinking about library betrayal.

So there are two ways around this. Setup a query with all the static imports and a reference to MoreLINQ and save it. Next time you need to work or experiment with MoreLINQ in LINQPad, open that query and select New Query, same properties from the File menu, or just press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>N</kbd> as a short-cut.

Alternatively, create a code snippet like the following:

Put that into your snippets directory and then type morelinq followed by <kbd>Tab</kbd> in any query to get all your static imports and MoreLINQ references added magically. The only unfortunate thing is that LINQPad’s import clean-up function is buggy as it removes those static imports whenever you ask for a clean-up but this is something to be taken up with @albahari and LINQPad’s support.

You can do something similar with code snippets in Visual Studio. Import the following code snippet:

You do that by usually putting it in "Code Snippets\Visual C#\My Code Snippets" under the Visual Studio version folder in your documents folder. Then whenever you need those static imports, press <kbd>Ctrl</kbd>+<kbd>K</kbd> followed by <kbd>Ctrl</kbd>+<kbd>X</kbd> to insert the snippet. Use all the extension methods you need (they’ll be all discoverable and available at this point) and finally press <kbd>Ctrl</kbd>+<kbd>R</kbd> followed by <kbd>Ctrl</kbd>+<kbd>G</kbd> to get rid of those static imports that you never used; rinse and repeat.

This takes care of the following point as well:

  1. Once you add your first static import and remove using MoreLinq, Intellisense will stop suggesting available operators.

As for:

3. Usually when a conflict occurs in a file, you already have used several MoreLinq operators, so having been faced with necessity to first change them all to static imports before being able to continue does not provoke a smile.

Again, drop the MoreLinq import, insert the static imports via the code snippet and then ask Visual Studio to remove the unused ones. This will take 10 seconds. 🚀

2. It ruins the flow of typing queries.

Code snippets can be inserted while in the middle of your typing and the imports will go in the right location at the top of the file without breaking your flow.

In closing, I’d like to repeat the following:

Perhaps and eventually, this is an issue to be taken up with the C# design team rather than libraries with extensions. Meanwhile, I propose instead to fix this via tooling if someone has the itch and the time. This can be done, for example, as a C# analyser for the IDE. It could be, I think, made to be MoreLINQ-specific or not.

So while an analyzer would be cool and perhaps more involved, I hope I’ve demonstrated that code snippets can be used as a lightweight alternative to alleviate some of the pain.

Furthermore it seems that some cases of using methods from both Linq and MoreLinq in the same file are only resolvable by calling the extension method from the static class name directly

You still haven’t called out these cases specifically. I’m curious to learn why I haven’t experienced the same trouble.

Hi atifaziz,

I ran into the case again today so thought I’d share. In this specific case, I have a class in which I’m using System.Linq’s Take(), and MoreLinq’s Slice(), and therefore have imported both System.Linq and MoreLinq.Extensions (or just MoreLinq results in the same problem). Then I go to use TakeLast(), and that’s where the ambiguity error occurs. What can I do to solve it? The easiest way to resolve this that I know of is to scroll to the top of the file, and modify using MoreLinq; to using static MoreLinq.Extensions.SliceExtension;, and then continue to manually add other such static usings for each additional MoreLinq extension method I wish to use, including ones that do not conflict with System.Linq (since otherwise I would need the using MoreLinq statement again which would bring back the ambiguities. A lot of people, probably most, are used to not disrupting coding flow by going to the top and manually adding using’s, instead using auto-completion for that. So it would make a very noticeable difference in usability.

I would also add that, while maybe only 10 people have reported this issue, keep in mind that only a small fraction of people finding trouble like this will take the time to go report the issue or join a discussion. If 1 out of 100 people do that, then you have maybe 1000 people running into it, and also these are most likely your main/biggest users, because they’re using a variety of MoreLinq calls within the same class.

What it the summary of approach chosen to tackle the issue? I fully support the idea that MoreLINQ should not conflict with corefx, but it is fine to conflict with other libraries.

@voxoid0 @atifaziz

Thanks for this discussion.

…keep in mind that only a small fraction of people finding trouble like this will take the time to go report the issue or join a discussion. If 1 out of 100 people do that, then you have maybe 1000 people running into it…

Here’s another 1000 users. 😃 I have refrained from commenting for a while. I am a little perplexed about the stance of not dropping overlapping methods from a major release. The tooling (e.g. VS and ReSharper) don’t do a good job of disambiguating these, and the manual intervention, does break flow.

Might it be worth it to introduce a MoreLinq.Core that does not have overlapping extensions (and possibly experimental extensions for that matter)? That preserves the MoreLinq package in its current form.

Just a thought…

I ran into the case again today so thought I’d share.

@voxoid0 Thanks for taking the time to do that.

What can I do to solve it? The easiest way to resolve this that I know of is to scroll to the top of the file, and modify using MoreLinq; to using static MoreLinq.Extensions.SliceExtension;, and then continue to manually add other such static usings for each additional MoreLinq extension method I wish to use, including ones that do not conflict with System.Linq…A lot of people, probably most, are used to not disrupting coding flow by going to the top and manually adding using’s, instead using auto-completion for that. So it would make a very noticeable difference in usability.

Did you read about the quick-fix solutions I offered with code snippets earlier? That’s the only easy way I know of without much investment on anyone’s end, although it did cost me time to think, investigate and share that option.

Add me to this “horde”. I concur with others who believe that using static is not a long-term solution. Deprecation and/or disambiguation is.

To me it would make sense to avoid publishing ambiguities with the library you’re “extending” (System.Linq), especially when there’s tooling available exactly for that purpose.

Resorting to snippets feels like a clunky work-around where the problem is moved to the users of this library, as they need to be configured and require some maintenance for library updates. I’m having a hard time recommending this library to our junior developers when it needs all these additional side-notes on its usage. And with future .NET versions, we’ll probably only run into more and more conflicts.

Just imagine the powerful experience if you could always simply use the extension you’re looking for and the MoreLinq library would only supplement the missing ones for the .NET version you’re currently targeting. And also when ‘upgrading’ your project to a later .NET version, you would not have to deal with all these ambiguities that suddenly pop up.

I’m not sure yet what it would look like exactly, but would the author be open to a PR that implements a multi-target build? I’d be happy to give it a go.

Or perhaps we just decide using MoreLINQ isn’t worth it since it conflicts with System.Linq and never start.

I apologise for the complete radio silence (priorities, COVID & life that happens when you’re busy making plans), but I think it’s time to conclude this issue so that the suspense doesn’t kill anyone. 😁

I have been keeping an eye on the comments and I am quite sure to disappoint everyone here (without it being my intention) by saying that I haven’t found any compelling arguments that make it worth my time beyond what was done (with thought, care and tremendous effort) to avoid conflicts in the first place. This issue has been open for a long time, so I hope you don’t find this to be an act of impulse. I have also not seen any input from other long-time/substantial contributors to MoreLINQ and there’s been plenty of time for that, so for better or worse, I am stepping in to make the call. Some of you have expressed abandonment of MoreLINQ and many more may take the same course right after this conclusion, and that’s perfectly okay.

I would personally like to thank @viceroypenguin for taking the time and effort to have gone the distance and provided a solution with some solid thinking behind it. It’s unfortunate that in spite of the cleverness of his solution (that I seriously contemplated adopting at one time), it had to eventually resort to renaming of methods to avoid conflicts with identically named but behaviourally different methods that have cropped up with more recent versions of .NET. This is not a chase I’m willing to do, but I am glad that @viceroypenguin is publishing SuperLinq and that the community has an alternative.

All that said, some methods have 100% compatibility in name and behaviour, like ToHashSet, and may be safe to drop from the next major version when breaking changes are fair to introduce.


PS Please bear in mind that many people have dedicated enormous amounts of time maintaining MoreLINQ since well over a decade so be kind, thoughtful and respectful in your reactions should you feel the need to offer them.

The number of methods overlapping with Enumerable is growing, which means the frustration is growing. I fully support this project, and I can understand the reluctance (maintenance issue) of removing methods, but when it’s so many, this burden is shifted from the few maintainers to the many consumers, which seriously mars the developer experience.

Just a few of the methods I’ve had issues with: Pipe(), Prepend(), Append(), ToHashSet(), TakeLast().

Think of it this way: the project was born (I assume) out of a need/want to fill in the gaps in the original LINQ library. If those gaps are being filled, then the duplicates in Morelinq just become a burden.

Of the hundreds of OS packages/libraries I’ve used I can’t think of any other project with this issue.

LINQPad 6 added a neat feature that allows one query to reference another using a #load directive. FWIW, I’ve created a new repository that, once cloned, makes MoreLINQ as easy as adding the following line on top of your LINQPad query:

#load "MoreLINQ\load\MoreLinq.linq"

And you shouldn’t run into conflicts (due to explicit and static imports) with same named LINQ extensions in .NET Core 3.

I have this problem as well. I know that whenever I need to convert an enumerable to a hash set (ToHashSet(), of course), it’s game over. I have to go remove the using MoreLinq; line, fix the ToHashSet() invocation, and then just see how badly my code broke because half of it was depending on a MoreLinq method. And since I don’t retain the full list of methods in my head, whenever I want to make a query with a new method, I have to come to the browser, search the MoreLinq documentation and see if a method fits what I’m trying to do, and then go back to Visual Studio to add the resepective using static to finally placate the compiler.

All, I have submitted PR #749 which would alleviate these issues. We don’t need to rename methods or hide them. We simply have to remove the this additive so that they are no longer extension methods in newer frameworks. Then there is no compile-time conflict, but any run-time dependencies still exist if code was compiled on an earlier framework and run on a newer framework.

Just found out about MoreLinq today from a StackOverflow question about finding the max object in a list. Library looked interesting and so I added it to my project and immediately ran into this issue with a conflict with TakeLast. For a library that extends System.Linq I would expect it to sunset or rename conflicting methods as new versions are released. Perhaps a different package named MoreLinq.Core (as someone else suggested) but the static approach seems sub-optimal.

Add me to this “horde”. I concur with others who believe that using static is not a long-term solution. Deprecation and/or disambiguation is.

Same for me.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using MoreLinq;

namespace ExampleCodeGenApp.Extensions
{
    public static class MoreLinqNoConflictExtensions
    {
        public static IExtremaEnumerable<T> MinByMoreLinq<T, TKey>(this IEnumerable<T> collection, Func<T, TKey> func)
        {
            return collection.MinBy(func);
        }

        public static IExtremaEnumerable<T> MaxByMoreLinq<T, TKey>(this IEnumerable<T> collection, Func<T, TKey> func)
        {
            return collection.MaxBy(func);
        }
    }
}

@thomaslevesque - the binary compatibility is not due to concerns about the host project. As you stated, many people recompile when upgrading a library, and obviously recompile when they change a TFM.

Binary compatibility is important due to other dependent projects. Consider the case: MyProject -> ThirdParty -> Linq.Extras, while MyProject also takes a direct dependency on Linq.Extras. If ThirdParty is built against the 4.6 TFM and is never rebuilt, then it will have a binary dependency on the Prepend() existing in Linq.Extras. However, because MyProject is upgraded to the 5.0 TFM, it relies on a version of Linq.Extras that does not include Prepend(). Then you find a runtime crash MethodNotFoundException when ThirdParty attempts to make the call to Prepend().

I’m abandoning use of MoreLinq in my project due to this issue.

Add me to the horde. Thank you TakeLast.

Normally I wouldn’t make a 👍 comment, but since the maintainer explicitly said he’s waiting for the hordes of users to complain about this, I figured I’d give my feedback. I didn’t add MoreLinq to the codebase, but I’m making a change and ran into a conflict with TakeLast, and it saddens me that I had to spend time on this. I’m surprised by the stance that conflicting with System.Linq when importing MoreLinq is “OK”. I’m sure it’s annoying to always be playing catchup with additions to System.Linq, but that’s the nature of the beast when making a library that extends something upstream, and this stance is punishing users who want to use the latest .NET bits.

One option is to stay independent of a 3rd party library and implement the methods you need yourself. You could search the open source if you need a guidance on how to do that, but remember that there is learning and growing in implementing them yourself.

At least that’s the direction I’ve decided to take. I’ve opted out from recommending to my work colleagues a library that has more and more conflicts with .NET with each passing year that it refuses to solve properly. Besides, LINQ is getting more and more useful methods in native .NET anyways, an external library is becoming less mandatory.

P.S. I’m sorry to post this negative opinion here. I appreciate and support open source, but I cannot shed the feeling of disappointment with how this issue was mishandled.

@Zofware FYI I have released morelinq.temp with the fix in #749 until it is merged to mainline and released.

The maintainers have been silent on the issue for well over a year, so I suspect not. I have not yet decided what to do about that.

@MatthewSteeples - I have updated according. I chose .MaxElementsBy() as a naming for the method. I have published this to nuget under version 4.0.0 (since this is a breaking change). Let me know if this helps!

I just had to solve similar issue in one of my own utility packages, which has ToHashSet extension method. That method was introduced to System.Linq.Enumerable in .NET Standard 2.1.

Simply removing or renaming the method and doing a multi-target build doesn’t work properly. The problem are transitive dependencies to the utility package.

Let’s say you have XmlParsingUtils package which dependes on CollectionUtils package (which has ToHashSet extension method). CollectionUtils has multi-target build implemented, for .NET Standard 2.0 and .NET Standard 2.1 targets. The 2.1 target renames the offending ToHashSet method. Additionally, XmlParsingUtils package doesn’t implement multi-target build because it doesn’t care about any changes between .NET Standard 2.0 and 2.1.

Now, let’s say you have the application targeting .NET Core 3.1 which references XmlParsingUtils package. The build process for your application will ultimately resolve CollectionUtils package which targets .NET Standard 2.1 and things will break at runtime with System.MissingMethodException when the code from XmlParsingUtils package tries to call the method it expects to be there.

Possible solution: What seems to work correctly is using multi-targeting and conditional compilation to make ToHashSet method regular (non-extension) method for .NET Standard 2.1 target. That way, the runtime can correctly link all the methods used. Furthermore, users referencing CollectionUtils package who target .NET Standard 2.1 or higher will will not get extension method resolution conflicts because for them (or rather, their compiler), ToHashSet is not extension method at all.

This makes for a nice user experience if your extension method has the same semantics as the one from the framework.