Skip to content

Commit

Permalink
Mirror changes from dotnet/coreclr (dotnet/corefx#28534)
Browse files Browse the repository at this point in the history
* Adding Memory.Pin() to eventually replace Memory.Retain(bool) (dotnet/corefx#17269)

* Adding Memory.Pin() to eventually replace Memory.Retain(bool)

* Fix copy/paste error and return default for when object is null.

* Fix XML comments.

Signed-off-by: dotnet-bot-corefx-mirror <[email protected]>

* Add Memory.Pin to the ref and add tests.

* Add Pin tests

* Update calls to Retain(true) to now call Pin()

* Remove extra space in tests.

* Add Pin to the ApiCompatBaseline for uapaot.


Commit migrated from dotnet/corefx@bb5c468
dotnet-bot authored and ahsonkhan committed Mar 29, 2018
1 parent 68e8e69 commit c31efe6
Showing 17 changed files with 345 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -232,7 +232,7 @@ internal static int Encrypt(SafeSslHandle context, ReadOnlyMemory<byte> input, r
int retVal;
unsafe
{
using (MemoryHandle handle = input.Retain(pin: true))
using (MemoryHandle handle = input.Pin())
{
retVal = Ssl.SslWrite(context, (byte*)handle.Pointer, input.Length);
}
Original file line number Diff line number Diff line change
@@ -100,7 +100,7 @@ internal unsafe void SetInput(ReadOnlyMemory<byte> inputBuffer)

lock (SyncLock)
{
_inputBufferHandle = inputBuffer.Retain(pin: true);
_inputBufferHandle = inputBuffer.Pin();

_zlibStream.NextIn = (IntPtr)_inputBufferHandle.Pointer;
_zlibStream.AvailIn = (uint)inputBuffer.Length;
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ protected PipeCompletionSource(ThreadPoolBoundHandle handle, ReadOnlyMemory<byte
_threadPoolBinding = handle;
_state = NoResult;

_pinnedMemory = bufferToPin.Retain(pin: true);
_pinnedMemory = bufferToPin.Pin();
_overlapped = _threadPoolBinding.AllocateNativeOverlapped((errorCode, numBytes, pOverlapped) =>
{
var completionSource = (PipeCompletionSource<TResult>)ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
@@ -107,6 +107,7 @@ public void CopyTo(System.Memory<T> destination) { }
public static implicit operator System.Memory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.Memory<T> memory) { throw null; }
public static implicit operator System.Memory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.Memory<T> Slice(int start) { throw null; }
public System.Memory<T> Slice(int start, int length) { throw null; }
@@ -131,6 +132,7 @@ public void CopyTo(System.Memory<T> destination) { }
public override int GetHashCode() { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start, int length) { throw null; }
133 changes: 133 additions & 0 deletions src/libraries/System.Memory/tests/Memory/Pin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using Xunit;

namespace System.MemoryTests
{
public static partial class MemoryTests
{
[Fact]
public static void MemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
Memory<int> memory = array;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayPin()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Fact]
public static void DefaultMemoryPin()
{
Memory<int> memory = default;
MemoryHandle handle = memory.Pin();
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
Memory<int> memory = array;
memory = memory.Slice(1);
MemoryHandle handle = memory.Pin();
Span<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
Memory<int> memory = owner.Memory;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
Memory<int> memory = owner.Memory.Slice(1);
MemoryHandle handle = memory.Pin();
Span<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}
}
}
48 changes: 24 additions & 24 deletions src/libraries/System.Memory/tests/Memory/Retain.cs
Original file line number Diff line number Diff line change
@@ -45,6 +45,30 @@ public static void MemoryRetainWithPinning()
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayRetainWithPinning()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Retain(pin: true);
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
Memory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryRetainWithPinningAndSlice()
{
@@ -110,15 +134,6 @@ public static void OwnedMemoryRetainWithPinning()
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayRetainWithPinning()
{
Memory<int> memory = new int[0];
MemoryHandle handle = memory.Retain(pin: true);
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Fact]
public static void OwnedMemoryRetainWithPinningAndSlice()
{
@@ -146,20 +161,5 @@ public static void OwnedMemoryRetainWithPinningAndSlice()
}
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
Memory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}
}
}
9 changes: 2 additions & 7 deletions src/libraries/System.Memory/tests/Memory/Strings.cs
Original file line number Diff line number Diff line change
@@ -57,18 +57,13 @@ public static void Memory_Slice_MatchesSubstring(string input, int offset, int c
}

[Fact]
public static unsafe void Memory_Retain_ExpectedPointerValue()
public static unsafe void Memory_Pin_ExpectedPointerValue()
{
string input = "0123456789";
ReadOnlyMemory<char> readonlyMemory = input.AsMemory();
Memory<char> m = MemoryMarshal.AsMemory(readonlyMemory);

using (MemoryHandle h = m.Retain(pin: false))
{
Assert.Equal(IntPtr.Zero, (IntPtr)h.Pointer);
}

using (MemoryHandle h = m.Retain(pin: true))
using (MemoryHandle h = m.Pin())
{
GC.Collect();
fixed (char* ptr = input)
23 changes: 13 additions & 10 deletions src/libraries/System.Memory/tests/MemoryMarshal/AsMemory.cs
Original file line number Diff line number Diff line change
@@ -39,17 +39,17 @@ public static IEnumerable<object[]> ReadOnlyMemoryCharInstances()

[Theory]
[MemberData(nameof(ReadOnlyMemoryInt32Instances))]
public static void AsMemory_Roundtrips(ReadOnlyMemory<int> readOnlyMemory) => AsMemory_Roundtrips_Core(readOnlyMemory);
public static void AsMemory_Roundtrips(ReadOnlyMemory<int> readOnlyMemory) => AsMemory_Roundtrips_Core(readOnlyMemory, true);

[Theory]
[MemberData(nameof(ReadOnlyMemoryObjectInstances))]
public static void AsMemory_Roundtrips(ReadOnlyMemory<object> readOnlyMemory) => AsMemory_Roundtrips_Core(readOnlyMemory);
public static void AsMemory_Roundtrips(ReadOnlyMemory<object> readOnlyMemory) => AsMemory_Roundtrips_Core(readOnlyMemory, false);

[Theory]
[MemberData(nameof(ReadOnlyMemoryCharInstances))]
public static void AsMemory_Roundtrips(ReadOnlyMemory<char> readOnlyMemory)
{
AsMemory_Roundtrips_Core(readOnlyMemory);
AsMemory_Roundtrips_Core(readOnlyMemory, true);

Memory<char> memory = MemoryMarshal.AsMemory(readOnlyMemory);
ReadOnlyMemory<char> readOnlyClone = memory;
@@ -66,7 +66,7 @@ public static void AsMemory_Roundtrips(ReadOnlyMemory<char> readOnlyMemory)
}
}

private static unsafe void AsMemory_Roundtrips_Core<T>(ReadOnlyMemory<T> readOnlyMemory)
private static unsafe void AsMemory_Roundtrips_Core<T>(ReadOnlyMemory<T> readOnlyMemory, bool canBePinned)
{
Memory<T> memory = MemoryMarshal.AsMemory(readOnlyMemory);
ReadOnlyMemory<T> readOnlyClone = memory;
@@ -87,13 +87,16 @@ private static unsafe void AsMemory_Roundtrips_Core<T>(ReadOnlyMemory<T> readOnl
Assert.Equal(array1.Offset, array2.Offset);
Assert.Equal(array1.Count, array2.Count);

// Retain
using (MemoryHandle readOnlyMemoryHandle = readOnlyMemory.Retain())
using (MemoryHandle readOnlyCloneHandle = readOnlyMemory.Retain())
using (MemoryHandle memoryHandle = readOnlyMemory.Retain())
if (canBePinned)
{
Assert.Equal((IntPtr)readOnlyMemoryHandle.Pointer, (IntPtr)readOnlyCloneHandle.Pointer);
Assert.Equal((IntPtr)readOnlyMemoryHandle.Pointer, (IntPtr)memoryHandle.Pointer);
// Pin
using (MemoryHandle readOnlyMemoryHandle = readOnlyMemory.Pin())
using (MemoryHandle readOnlyCloneHandle = readOnlyMemory.Pin())
using (MemoryHandle memoryHandle = readOnlyMemory.Pin())
{
Assert.Equal((IntPtr)readOnlyMemoryHandle.Pointer, (IntPtr)readOnlyCloneHandle.Pointer);
Assert.Equal((IntPtr)readOnlyMemoryHandle.Pointer, (IntPtr)memoryHandle.Pointer);
}
}
}
}
133 changes: 133 additions & 0 deletions src/libraries/System.Memory/tests/ReadOnlyMemory/Pin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using Xunit;

namespace System.MemoryTests
{
public static partial class ReadOnlyMemoryTests
{
[Fact]
public static void MemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
ReadOnlyMemory<int> memory = array;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void MemoryFromEmptyArrayPin()
{
ReadOnlyMemory<int> memory = new int[0];
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
handle.Dispose();
}

[Fact]
public static void DefaultMemoryPin()
{
ReadOnlyMemory<int> memory = default;
MemoryHandle handle = memory.Pin();
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
ReadOnlyMemory<int> memory = array;
memory = memory.Slice(1);
MemoryHandle handle = memory.Pin();
ReadOnlySpan<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPin()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
ReadOnlyMemory<int> memory = owner.Memory;
MemoryHandle handle = memory.Pin();
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i], pointer[i]);
}
}
handle.Dispose();
}

[Fact]
public static void OwnedMemoryPinAndSlice()
{
int[] array = { 1, 2, 3, 4, 5 };
OwnedMemory<int> owner = new CustomMemoryForTest<int>(array);
ReadOnlyMemory<int> memory = owner.Memory.Slice(1);
MemoryHandle handle = memory.Pin();
ReadOnlySpan<int> span = memory.Span;
Assert.True(handle.HasPointer);
unsafe
{
int* pointer = (int*)handle.Pointer;

GC.Collect();

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], pointer[i]);
}

for (int i = 0; i < memory.Length; i++)
{
Assert.Equal(array[i + 1], span[i]);
}
}
handle.Dispose();
}
}
}
30 changes: 15 additions & 15 deletions src/libraries/System.Memory/tests/ReadOnlyMemory/Retain.cs
Original file line number Diff line number Diff line change
@@ -54,6 +54,21 @@ public static void MemoryFromEmptyArrayRetainWithPinning()
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
ReadOnlyMemory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}

[Fact]
public static void MemoryRetainWithPinningAndSlice()
{
@@ -146,20 +161,5 @@ public static void OwnedMemoryRetainWithPinningAndSlice()
}
handle.Dispose();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public static void DefaultMemoryRetain(bool pin)
{
ReadOnlyMemory<int> memory = default;
MemoryHandle handle = memory.Retain(pin: pin);
Assert.False(handle.HasPointer);
unsafe
{
Assert.True(handle.Pointer == null);
}
handle.Dispose();
}
}
}
11 changes: 3 additions & 8 deletions src/libraries/System.Memory/tests/ReadOnlyMemory/Strings.cs
Original file line number Diff line number Diff line change
@@ -94,17 +94,12 @@ public static void AsReadOnlyMemory_TryGetArray_ReturnsFalse()
}

[Fact]
public static unsafe void AsReadOnlyMemory_Retain_ExpectedPointerValue()
public static unsafe void AsReadOnlyMemory_Pin_ExpectedPointerValue()
{
string input = "0123456789";
ReadOnlyMemory<char> m = input.AsMemory();

using (MemoryHandle h = m.Retain(pin: false))
{
Assert.Equal(IntPtr.Zero, (IntPtr)h.Pointer);
}

using (MemoryHandle h = m.Retain(pin: true))
using (MemoryHandle h = m.Pin())
{
GC.Collect();
fixed (char* ptr = input)
@@ -137,7 +132,7 @@ public static unsafe void AsReadOnlyMemory_PointerAndLength(string text, int sta

Assert.Equal(length, m.Length);

using (MemoryHandle h = m.Retain(pin: true))
using (MemoryHandle h = m.Pin())
{
fixed (char* pText = text)
{
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/tests/System.Memory.Tests.csproj
Original file line number Diff line number Diff line change
@@ -163,6 +163,7 @@
<Compile Include="Memory\GetHashCode.cs" />
<Compile Include="Memory\ImplicitConversion.cs" />
<Compile Include="Memory\OwnedMemory.cs" />
<Compile Include="Memory\Pin.cs" />
<Compile Include="Memory\Retain.cs" />
<Compile Include="Memory\Slice.cs" />
<Compile Include="Memory\Span.cs" />
@@ -193,6 +194,7 @@
<Compile Include="ReadOnlyMemory\Equality.cs" />
<Compile Include="ReadOnlyMemory\GetHashCode.cs" />
<Compile Include="ReadOnlyMemory\ImplicitConversion.cs" />
<Compile Include="ReadOnlyMemory\Pin.cs" />
<Compile Include="ReadOnlyMemory\Retain.cs" />
<Compile Include="ReadOnlyMemory\Slice.cs" />
<Compile Include="ReadOnlyMemory\Span.cs" />
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ public static SecurityStatusPal EncryptMessage(

unsafe
{
MemoryHandle memHandle = input.Retain(pin: true);
MemoryHandle memHandle = input.Pin();
try
{
PAL_TlsIo status;
Original file line number Diff line number Diff line change
@@ -183,7 +183,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s
// Return pending and we will continue in the completion port callback.
if (_singleBufferHandleState == SingleBufferHandleState.InProcess)
{
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandle = _buffer.Pin();
_singleBufferHandleState = SingleBufferHandleState.Set;
}
return SocketError.IOPending;
@@ -199,7 +199,7 @@ internal unsafe SocketError DoOperationAccept(Socket socket, SafeCloseSocket han
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
_singleBufferHandle = buffer.Retain(pin: true);
_singleBufferHandle = buffer.Pin();
_singleBufferHandleState = SingleBufferHandleState.Set;

bool success = socket.AcceptEx(
@@ -234,7 +234,7 @@ internal unsafe SocketError DoOperationConnect(Socket socket, SafeCloseSocket ha
try
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandle = _buffer.Pin();
_singleBufferHandleState = SingleBufferHandleState.Set;

bool success = socket.ConnectEx(
@@ -472,7 +472,7 @@ internal unsafe SocketError DoOperationReceiveMessageFrom(Socket socket, SafeClo
}

Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandle = _buffer.Pin();
_singleBufferHandleState = SingleBufferHandleState.Set;

_wsaRecvMsgWSABufferArray[0].Pointer = (IntPtr)_singleBufferHandle.Pointer;
2 changes: 2 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
@@ -1700,6 +1700,7 @@ public void CopyTo(System.Memory<T> destination) { }
public static implicit operator System.Memory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.Memory<T> memory) { throw null; }
public static implicit operator System.Memory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.Memory<T> Slice(int start) { throw null; }
public System.Memory<T> Slice(int start, int length) { throw null; }
@@ -1947,6 +1948,7 @@ public void CopyTo(System.Memory<T> destination) { }
public override int GetHashCode() { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (System.ArraySegment<T> segment) { throw null; }
public static implicit operator System.ReadOnlyMemory<T> (T[] array) { throw null; }
public System.Buffers.MemoryHandle Pin() { throw null; }
public System.Buffers.MemoryHandle Retain(bool pin = false) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start) { throw null; }
public System.ReadOnlyMemory<T> Slice(int start, int length) { throw null; }
4 changes: 4 additions & 0 deletions src/libraries/System.Runtime/src/ApiCompatBaseline.uapaot.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Compat issues with assembly System.Runtime:
MembersMustExist : Member 'System.Memory<T>.Pin()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'System.ReadOnlyMemory<T>.Pin()' does not exist in the implementation but it does exist in the contract.
Total Issues: 2
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ TypesMustExist : Type 'System.Security.Cryptography.ECCurve' does not exist in t
TypesMustExist : Type 'System.Security.Cryptography.ECParameters' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'System.Security.Cryptography.ECPoint' does not exist in the implementation but it does exist in the contract.

Compat issues with assembly System.Runtime:
MembersMustExist : Member 'System.Memory<T>.Pin()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'System.ReadOnlyMemory<T>.Pin()' does not exist in the implementation but it does exist in the contract.

# Compat issues complaining about class vs delegate and class vs struct are because of a bug in APICompat tool where the implementation is picking
# the wrong core assembly. It is picking System.Runtime instead of System.Private.CoreLib, there isn't any straight forward way to fix so baselining.
TypeCannotChangeClassification : Type 'System.Action<T1, T2, T3, T4, T5, T6, T7, T8, T9>' is a 'class' in the implementation but is a 'delegate' in the contract.

0 comments on commit c31efe6

Please sign in to comment.