Sergio0694 / PolySharp

PolySharp provides generated, source-only polyfills for C# language features, to easily use all runtime-agnostic features downlevel. Add a reference, set your C# version to latest and have fun! 🚀
MIT License
1.75k stars 45 forks source link

Polyfill instance methods with extensions #6

Open Tyrrrz opened 1 year ago

Tyrrrz commented 1 year ago

Description (optional)

First off, awesome idea for the project. I was hoping someone would do it :)

I was wondering if you had any plans to polyfill certain built-in instance methods with extensions. For example, these are some of the polyfills I have in my projects:

https://github.com/Tyrrrz/CliWrap/blob/ccc3087f0744dce17899a433591b3e50fd18e90b/CliWrap/Utils/Polyfills.Collections.cs#L1-L17

https://github.com/Tyrrrz/CliWrap/blob/ccc3087f0744dce17899a433591b3e50fd18e90b/CliWrap/Utils/Polyfills.Streams.cs#L1-L57

They allow me to seamlessly call methods only available on newer frameworks, while polyfilling the functionality on older frameworks.

Rationale

Besides attributes, this could be an additional aspect of polyfilling that might be worth exploring. As a user, I can see great benefit in this as it would allow me to replace my own polyfills that I copy around between projects with a single package.

Proposed API

I propose a similar approach that I've been using: extensions in a global namespace. This makes the polyfill callsites fully source-compatible with natural callsites.

Drawbacks

This would require to figure out how to actually implement certain polyfills using existing functionality, and might not be trivial in some cases.

Alternatives

Just writing polyfills manually.

Other thoughts

None.

menees commented 1 year ago

I agree. This is a great project, and it would be nice if it also filled in some of the new .NET instance methods via extensions.

I use the Microsoft.CodeAnalysis.NetAnalyzers code analysis rules on projects that target both .NET Framework 4.8 and .NET 6.0. Rule CA2249 recommends using Contains(char) instead of IndexOf(char) >= 0, which is great for .NET 6.0. But I have to add a Contains(char) polyfill for my .NET Framework targets to make this work. It would be great if Poly# provided implementations like these:

/// <summary>
/// Returns a value indicating whether a specified character occurs within this string.
/// </summary>
/// <param name="text">The string to search in.</param>
/// <param name="value">The character to seek.</param>
/// <remarks>This method performs an ordinal (case-sensitive and culture-insensitive) comparison.</remarks>
/// <returns>true if the value parameter occurs within this string; otherwise, false.</returns>
public static bool Contains(this string text, char value)
    => text.IndexOf(value) >= 0;

/// <summary>
/// Returns a value indicating whether a specified string occurs within this string, using the specified comparison rules.
/// </summary>
/// <param name="text">The string to search in.</param>
/// <param name="value">The string to seek.</param>
/// <param name="comparison">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns>true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false.</returns>
public static bool Contains(this string text, string value, StringComparison comparison)
    => text.IndexOf(value, comparison) >= 0;

/// <summary>
/// Determines whether the end of the <paramref name="text"/> string instance matches the specified character.
/// </summary>
/// <param name="text">The text to check.</param>
/// <param name="ch">The character to compare to the character at the end of <paramref name="text"/>.</param>
/// <returns>
/// True if <paramref name="text"/> is non-empty and ends with <paramref name="ch"/>.
/// False if <paramref name="text"/> is null, empty, or doesn't end with <paramref name="ch"/>.
/// </returns>
public static bool EndsWith(this string text, char ch)
    => text.Length > 0 && text[text.Length - 1] == ch;

/// <summary>
/// Determines whether the start of the <paramref name="text"/> string instance matches the specified character.
/// </summary>
/// <param name="text">The text to check.</param>
/// <param name="ch">The character to compare to the character at the start of <paramref name="text"/>.</param>
/// <returns>
/// True if <paramref name="text"/> is non-empty and starts with <paramref name="ch"/>.
/// False if <paramref name="text"/> is null, empty, or doesn't start with <paramref name="ch"/>.
/// </returns>
public static bool StartsWith(this string text, char ch)
    => text.Length > 0 && text[0] == ch;

And here are some unit tests using MSTest v2 and Shoudly:

[TestMethod]
public void ContainsChar()
{
    string.Empty.Contains('x').ShouldBeFalse();
    "Test".Contains('e').ShouldBeTrue();
    "Test".Contains('S').ShouldBeFalse();
}

[TestMethod]
public void ContainsString()
{
    string.Empty.Contains("x", StringComparison.OrdinalIgnoreCase).ShouldBeFalse();
    "Test".Contains("es", StringComparison.OrdinalIgnoreCase).ShouldBeTrue();
    "Test".Contains("ES", StringComparison.OrdinalIgnoreCase).ShouldBeTrue();
}

[TestMethod]
public void EndsWith()
{
    string.Empty.EndsWith('x').ShouldBeFalse();
    "Test".EndsWith('t').ShouldBeTrue();
    "Test".EndsWith('T').ShouldBeFalse();
}

[TestMethod]
public void StartsWith()
{
    string.Empty.StartsWith('x').ShouldBeFalse();
    "Test".StartsWith('t').ShouldBeFalse();
    "Test".StartsWith('T').ShouldBeTrue();
}
cremor commented 1 year ago

Here are quite a few examples of which polyfill extension methods would be possible: https://github.com/SimonCropp/Polyfill#extensions

Bouke commented 1 month ago

My project contains also various polyfills to bridge the gap. I have compared https://github.com/Sergio0694/PolySharp and https://github.com/meziantou/Meziantou.Polyfill, and I've found that:

What do people typically do here; go with the other projects? Define your own polyfills? Some other project that works in addition to Polysharp?