Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Microsoft.Toolkit.HighPerformance package #3128

Merged

Conversation

Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Feb 11, 2020

PR Type

What kind of change does this PR introduce?

  • Feature

What is the new behavior?

This PR introduces a new Microsoft.Toolkit.HighPerformance package, with a collection of types and extensions that can be used in high-performance scenarios.

NOTE: the API surface is subject to change. Also, each single API has extensive XML docs in the code, so if you want additional details on any of the new APIs, just look it up in its source file.

Below is a breakthrough of all the new types and APIs added in this PR:

Microsoft.Toolkit.HighPerformance.Buffers
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
    public sealed class MemoryOwner<T> : IMemoryOwner<T>
    {
        public static MemoryOwner<T> Empty;
        public static MemoryOwner<T> Allocate(int size);
        public static MemoryOwner<T> Allocate(int size, bool clear);
        public int Length;
        public Memory<T> Memory;
        public Span<T> Span;
        public ref T DangerousGetReference();
        public void Dispose();
        ~MemoryOwner();
    }

    public readonly ref struct SpanOwner<T>
    {
        public static SpanOwner<T> Empty;
        public static SpanOwner<T> Allocate(int size);
        public static SpanOwner<T> Allocate(int size, AllocationMode mode);
        public int Length;
        public Span<T> Span;
        public ref T DangerousGetReference();
        public void Dispose();
    }

    public enum AllocationMode
    {
        Default,
        Clear
    }
}
Microsoft.Toolkit.HighPerformance.Enumerables
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
    public ref struct Array2DColumnEnumerable<T>
    {
        public Array2DColumnEnumerable(T[,] array, int column);
        public Enumerator GetEnumerator();
        public T[] ToArray();

        public ref struct Enumerator
        {
            public Enumerator(T[,] array, int column);
            public bool MoveNext();
            public ref T Current { get; }
        }
    }

#if NETSTANDARD2_0
    public ref struct Array2DRowEnumerable<T>
    {
        public Array2DRowEnumerable(T[,] array, int row);
        public Enumerator GetEnumerator();
        public T[] ToArray();

        public ref struct Enumerator
        {
            public Enumerator(T[,] array, int row);
            public bool MoveNext();
            public ref T Current { get; }
        }
    }
#endif

    public readonly ref struct ReadOnlySpanEnumerable<T>
    {
        public ReadOnlySpanEnumerable(ReadOnlySpan<T> span);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public Enumerator(ReadOnlySpan<T> span);
            public bool MoveNext();
            public (int Index, T Value) Current;
        }
    }

    public ref struct ReadOnlySpanTokenizer<T> where T : IEquatable<T>
    {
        public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public Enumerator(ReadOnlySpan<T> span, T separator);
            public bool MoveNext();
            public ReadOnlySpan<T> Current;
        }
    }

    public readonly ref struct SpanEnumerable<T>
    {
        public SpanEnumerable(Span<T> span);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public Enumerator(Span<T> span);
            public bool MoveNext();
            public Item Current;
        }

        public readonly ref struct Item
        {
#if NETSTANDARD2_1
            public Item(Span<T> span);
#else;
            public Item(Span<T> span, int index);
#endif
            public ref T Value;
            public int Index;
        }
    }

    public ref struct SpanTokenizer<T> where T : IEquatable<T>
    {
        public SpanTokenizer(Span<T> span, T separator);
        public Enumerator GetEnumerator();

        public ref struct Enumerator
        {
            public Enumerator(Span<T> span, T separator);
            public bool MoveNext();
            public Span<T> Current;
        }
    }
}
Microsoft.Toolkit.HighPerformance.Extensions
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
    public static class ArrayExtensions
    {
        public static ref T DangerousGetReference<T>(this T[] array);
        public static ref T DangerousGetReference<T>(this T[,] array);
        public static ref T DangerousGetReferenceAt<T>(this T[] array, int i);
        public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j);
        public static int Count<T>(this T[] array, T value) where T : IEquatable<T>;
        public static SpanEnumerable<T> Enumerate<T>(this T[] array);
        public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator);
        public static int GetDjb2HashCode<T>(this T[] array) where T : notnull;
#if NETSTANDARD2_1
        public static Span<T> AsSpan<T>(this T[,] array);
        public static int Count<T>(this T[,] array, T value) where T : IEquatable<T>;
        public static int GetDjb2HashCode<T>(this T[,] array) where T : notnull;
#endif
        public static void Fill<T>(this T[,] array, T value, int row, int column, int width, int height);        
#if NETSTANDARD2_1
        public static Span<T> GetRow<T>(this T[,] array, int row);
#else
        public static Array2DRowEnumerable<T> GetRow<T>(this T[,] array, int row);
#endif
        public static Array2DColumnEnumerable<T> GetColumn<T>(this T[,] array, int column);
    }

    public static class ArrayPoolExtensions
    {
        public static void Resize<T>(this ArrayPool<T> pool, ref T[]? array, int newSize, bool clearArray = false);
    }

    public static class BoolExtensions
    {
        public static int ToInt(this bool flag);
    }

    public static class HashCodeExtensions
    {
        public static void Add<T>(ref this HashCode hashCode, T[] array)
#if NETSTANDARD2_1
            where T : notnull
#else
            where T : unmanaged;
#endif
        public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
#if NETSTANDARD2_1
            where T : notnull;
#else
            where T : unmanaged;
#endif
        public static void Add(ref this HashCode hashCode, string text);
    }

    public static class IMemoryOwnerExtensions
    {
        public static Stream AsStream(this IMemoryOwner<byte> memory);
    }

    public static class MemoryExtensions
    {
        public static Stream AsStream(this Memory<byte> memory);
    }

    public static class ReadOnlyMemoryExtensions
    {
        public static Stream AsStream(this ReadOnlyMemory<byte> memory);
    }

    public static class ReadOnlySpanExtensions
    {
        public static ref readonly T DangerousGetReference<T>(this ReadOnlySpan<T> span);
        public static ref readonly T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i);
        public static ReadOnlySpan<byte> AsBytes<T>(this ReadOnlySpan<T> span) where T : unmanaged;
        public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(this ReadOnlySpan<TFrom> span) where TFrom : struct where TTo : struct;
        public static int Count<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T>;
        public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span) where T : notnull;
        public static ReadOnlySpanEnumerable<T> Enumerate<T>(this ReadOnlySpan<T> span);
        public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T separator) where T : IEquatable<T>;
    }

    public static class SpanExtensions
    {
        public static ref T DangerousGetReference<T>(this Span<T> span);
        public static ref T DangerousGetReferenceAt<T>(this Span<T> span, int i);
        public static Span<byte> AsBytes<T>(this Span<T> span) where T : unmanaged;
        public static Span<TTo> Cast<TFrom, TTo>(this Span<TFrom> span) where TFrom : struct where TTo : struct;
        public static int Count<T>(this Span<T> span, T value) where T : IEquatable<T>;
        public static int GetDjb2HashCode<T>(this Span<T> span) where T : notnull;
        public static SpanEnumerable<T> Enumerate<T>(this Span<T> span);
        public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator) where T : IEquatable<T>;
    }

    public static class SpinLockExtensions
    {
        public static unsafe UnsafeLock Enter(SpinLock* spinLock);

        public unsafe ref struct UnsafeLock
        {
            public UnsafeLock(SpinLock* spinLock);
            public void Dispose();
        }

#if NETSTANDARD2_1
        public static Lock Enter(ref this SpinLock spinLock);
#else
        public static Lock Enter(object owner, ref SpinLock spinLock);
#endif

        public ref struct Lock
        {
#if NETSTANDARD2_1
            public Lock(ref SpinLock spinLock);
#else
            public Lock(object owner, ref SpinLock spinLock);
#endif
            public void Dispose();
        }
    }

    public static class StringExtensions
    {
        public static ref readonly char DangerousGetReference<T>(this string text);
        public static ref readonly char DangerousGetReferenceAt<T>(this string text, int i);
        public static int Count<T>(this string text, char c);
        public static ReadOnlySpanEnumerable<char> Enumerate(this string text);
        public static ReadOnlySpanTokenizer<char> Tokenize(this string text, char separator);
        public static int GetDjb2HashCode(this string text);
    }

    public static class ValueTypeExtensions
    {
        public static bool TryUnbox<T>(this object obj, out T value) where T : struct;
        public static T DangerousUnbox<T>(this object obj) where T : struct;
    }
}
Microsoft.Toolkit.HighPerformance.Helpers
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
    public static class BitHelper
    {
        public static bool HasFlag(uint value, int n);
        public static void SetFlag(ref uint value, int n, bool flag);
        public static uint SetFlag(uint value, int n, bool flag);
        public static bool HasFlag(ulong value, int n);
        public static void SetFlag(ref ulong value, int n, bool flag);
        public static ulong SetFlag(ulong value, int n, bool flag);
    }

    public struct HashCode<T>
#if NETSTANDARD2_1
        where T : notnull
#else
        where T : unmanaged
#endif
    {
        public static int Combine(ReadOnlySpan<T> span);
    }

    public interface IAction
    {
        void Invoke(int i);
    }

    public interface IAction2D
    {
        void Invoke(int i, int j);
    }

    public interface IInAction<T>
    {
        void Invoke(in T item);
    }

    public interface IRefAction<T>
    {
        void Invoke(ref T item);
    }

    public static partial class ParallelHelper
    {
#if NETSTANDARD2_1
        public static void For<TAction>(Range range)
            where TAction : struct, IAction;
        public static void For<TAction>(Range range, int minimumActionsPerThread)
            where TAction : struct, IAction;
        public static void For<TAction>(Range range, TAction action)
            where TAction : struct, IAction;
        public static void For<TAction>(Range range, TAction action, int minimumActionsPerThread)
            where TAction : struct, IAction;
#endif
        public static void For<TAction>(int start, int end)
            where TAction : struct, IAction;
        public static void For<TAction>(int start, int end, int minimumActionsPerThread)
            where TAction : struct, IAction;
        public static void For<TAction>(int start, int end, in TAction action)
            where TAction : struct, IAction;
        public static void For<TAction>(int start, int end, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IAction;
#if NETSTANDARD2_1
        public static void For2D<TAction>(Range i, Range j)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Range i, Range j, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Range i, Range j, in TAction action)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Range i, Range j, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
#endif
        public static void For2D<TAction>(Rectangle area)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Rectangle area, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Rectangle area, in TAction action)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(Rectangle area, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(int top, int bottom, int left, int right)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(int top, int bottom, int left, int right, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(int top, int bottom, int left, int right, in TAction action)
            where TAction : struct, IAction2D;
        public static void For2D<TAction>(int top, int bottom, int left, int right, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IAction2D;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory<TItem> memory)
            where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory<TItem> memory, int minimumActionsPerThread)
            where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory<TItem> memory, in TAction action)
            where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(ReadOnlyMemory<TItem> memory, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IInAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory<TItem> memory)
            where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory<TItem> memory, int minimumActionsPerThread)
            where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory<TItem> memory, in TAction action)
            where TAction : struct, IRefAction<TItem>;
        public static void ForEach<TItem, TAction>(Memory<TItem> memory, in TAction action, int minimumActionsPerThread)
            where TAction : struct, IRefAction<TItem>;
    }
}
Microsoft.Toolkit.HighPerformance
namespace Microsoft.Toolkit.HighPerformance
{
    public sealed class Box<T> where T : struct
    {
        public T Value;
        public static Box<T> GetFrom(object obj);
        public static Box<T> DangerousGetFrom(object obj);
        public static bool TryGetFrom(object obj, out Box<T>? box);
        public static implicit operator T(Box<T> box);
        public static implicit operator Box<T>(T value);
    }

    public readonly ref struct NullableRef<T>
    {
        public NullableRef(ref T value);
        public bool HasValue { get; }
        public ref T Value { get; }
    }

    public readonly ref struct NullableReadOnlyRef<T>
    {
        public NullableReadOnlyRef(in T value);
        public bool HasValue { get; }
        public ref readonly T Value { get; }
    }

    public readonly ref struct Ref<T>
    {
#if NETSTANDARD2_1
        public Ref(ref T value);
#else
        public Ref(object owner, ref T value);
#endif
        public ref T Value;
        public static implicit operator T(Ref<T> reference);
    }

    public readonly ref struct ReadOnlyRef<T>
    {
#if NETSTANDARD2_1
        public ReadOnlyRef(in T value);
#else
        public ReadOnlyRef(object owner, in T value);
#endif
        public ref readonly T Value;
        public static implicit operator ReadOnlyRef<T>(Ref<T> reference);
        public static implicit operator T(ReadOnlyRef<T> reference);
    }
}

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link: #301
  • Sample in sample app has been added / updated (for bug fixes / features)
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

Other information

cc. @michael-hawker from our previous conversation in the UWP Community Discord server.

The project targets both .NET Standard 2.1 and .NET Standard 2.0, so that it can both be available for UWP applications, as well as offering more advanced features for developers working on .NET Core 3.x. It uses the following packages:

  • System.Runtime.CompilerServices.Unsafe
  • System.Memory (only on .NET Standard 2.0)
  • Microsoft.Bcl.HashCode (only on .NET Standard 2.0)

Additionally, some APIs are just removed entirely when on .NET Standard 2.0, since they require runtime support that's not available on that target (eg the ByReference<T> types).

@ghost
Copy link

ghost commented Feb 11, 2020

Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌

@azchohfi
Copy link
Contributor

azchohfi commented Apr 2, 2020

I'm really glad you thoroughly added enough tests for this!

@Sergio0694
Copy link
Member Author

Ahahah yeah, I felt like a whole package like this would definitely require an extensive tests suite to make sure it behaved properly, especially with all the weird stuff going on. Besides, I figured if you guys were to really greenlight this whole project and trust me with building so niche and potentially dangerous APIs, I'd better make sure no to let you down and make things as thoroughly as I could! 😊

@azchohfi
Copy link
Contributor

azchohfi commented Apr 2, 2020

There seems to be some problem with Github. Should be back soon.

Also pushing this commit just to re-trigger the CI build, since GitHub went offline and caused the previous one to fail/half midway through
@azchohfi
Copy link
Contributor

azchohfi commented Apr 2, 2020

Failed: 0 (0.00%)
Passed: 1,765 (98.93%)
Other: 19 (1.07%)
Total: 1,784

Nice!

@Sergio0694
Copy link
Member Author

YES! 😄🎉🎉🎉🚀🚀🚀

@Sergio0694
Copy link
Member Author

@michael-hawker leaving some notes here since you mentioned you'd focus on naming/API surface in your review. I keep thinking about @aalmada's suggestion and I have to say on second though he did have a point there. To recap, these are the "ref-like" types included in this PR:

  • ByReference<T>
  • ReadOnlyByReference<T>
  • NullableByReference<T>
  • NullableReadOnlyByReference<T>

The original inspiration for the name was the ByReference<T> type in CoreCLR.

Now, I've explained my rationale for using these names in my previous comment (here), in short I followed the naming scheme for collection types (eg. Span<T> and ReadOnlySpan<T>). What Antão said is fair though: in this case the naming scheme is basically "by" + reference type, hence why ByReference<T> in CoreCLR, so it would also make sense to use ByReadOnlyReference<T> for when you're still "by" a reference, but a readonly one. With that naming scheme, we'd have:

  • ByReference<T>
  • ByReadOnlyReference<T>
  • NullableByReference<T>
  • NullableByReadOnlyReference<T>

This approach works too, but I've started wondering whether both these two naming schemes are a bit too much verbose, or just harder to read in general as they have pretty long names in general.

So, while thinking about this I had another thought. The ByReference<T> type is internal and will never be exposed publicly, so we actually don't have to keep naming "consistency" there at all. Becaue of this, what if we simplified the whole naming scheme to just use something that directly mapped to the type they represent in C#? Basically, in this case we'd end up with the following:

  • Ref<T>
  • ReadOnlyRef<T>
  • NullableRef<T>
  • NullableReadonlyRef<T>

This latter option has the advantage of being both much more compact (especially Ref<T> compared to ByReference<T>), as well as using literally the same exact name you'd use when just reading C# code, which should make it more intuitive to use as well. I mean, for a practical example:

int[] array = new int[10];

ref int myRef1 = ref array[0];

Ref<int> myRef2 = new Ref<int>(ref array[0]); // same thing

Here you can see the advantage of using this new naming scheme: ref int and Ref<int> map to the same concept and now also look basically the same in C# code, and also "sound" the same.

Wanted to hear your thoughts on this before actually going ahead and renaming the types in quesiton, on my end I have to say I'm really liking this idea as I think it simplifies things quite a bit 😄

@michael-hawker
Copy link
Member

@Sergio0694 like you new idea on simplifying the names down to match the base C# ref, that seems a lot more straight-forward and simpler.

We should also at least have a landing page in the Sample App, but let's add that to the follow-up PR or later.

@ghost ghost removed the needs attention 👋 label May 5, 2020
Copy link
Member

@michael-hawker michael-hawker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing! Thanks @Sergio0694. I know there's still some follow-up to be done in a smaller follow-on PR, so we can address any API comment/unit test comments there. 🎉🎉🎉

/// </summary>
/// <typeparam name="T">The type of value being boxed.</typeparam>
[DebuggerDisplay("{ToString(),raw}")]
public sealed class Box<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have some examples/docs at least inline here if not called out about why/where this could be used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 018f4a7.

/// </summary>
private T[]? array;

#pragma warning disable IDE0032
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave comments on warnings / reasoning

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c309019.

}

/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the Djb2 algorithm.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a see link to a proper source of explanation for this Djb2 algorithm?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's link to the list of algorithms here and the performance matrix and the reference implementation used here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And maybe this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the links! 😄
Done in c07112c.

// Main loop with 8 unrolled iterations
for (; i <= end8; i += 8)
{
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 0).GetHashCode());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add comment about this optimization here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good idea! 😊
Done in 09c8e88.

}

/// <summary>
/// Gets a content hash from the input <see cref="Span{T}"/> instance using the Djb2 algorithm.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference other comment with links or same links?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linked the docs for the base ReadOnlySpan<T> method, done in c07112c.

namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Combines the hash code of sequences of <typeparamref name="T"/> values into a single hash code.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to call out here as a remark or something that these codes are only guaranteed for the current execution session. Call out reference to other hashcode implementations for repeatable hashes. Probably best to just link out to the same remarks that the system hashcode has from here: https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c9a5c4c.

var buffer = MemoryOwner<int>.Allocate(127);

buffer.Dispose();
buffer.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be some checks here to make sure something has happened?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed during the stream (https://www.twitch.tv/xamlllama), I've added a comment to this in 54536e4 to indicate that the test is just to make sure this code doesn't crash.


Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, buffer.Length);
Assert.IsTrue(stream.CanWrite);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably validate that the stream is indeed empty without any data, right?

Likewise, we should also have tests where the original buffer contains some data, and we validate after conversion to a stream that the same data can be read back?

(We can do this in follow-on PR)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely! Done both in e8a9534.


[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_Tokenize_Csv()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to change these names later to Comma as CSV has other parsing implications 😋

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahahahah right 😄
Done in 6f488cd.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice information Admin...
i suggest if anyone want full version of latest software he should visit this site

@michael-hawker michael-hawker merged commit c65d648 into CommunityToolkit:master May 5, 2020
@Sergio0694
Copy link
Member Author

WE DID IT! 🎉🎉🎉

Thank you so much @michael-hawker and @azchohfi for the amazing opportunity and for your time reviewing this, I'm so happy to have been able to contribute with this new toolkit package! 😊

Now on to work on the follow up PR!
Can't wait for everyone to be able to try all of this out once the 6.1 release lands! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants