Added: C# Design Notes for Oct 1 and 3, 2018 #1925
Replies: 17 comments
-
Excited to see you guys discuss |
Beta Was this translation helpful? Give feedback.
-
With regards to the two potential interfaces for IEnumerator: Anyone who consumes However the performance advantages when consuming an I feel it's a case of spend a bit more time up front, benefit everywhere. |
Beta Was this translation helpful? Give feedback.
-
The perf problem with |
Beta Was this translation helpful? Give feedback.
-
If the "slow" version of E.g.: void PrintAll(IAsyncEnumerable<T> enumerable)
{
// GetSimpleAsyncEnumerator is an extension method
var enumerator = enumerable.GetSimpleAsyncEnumerator();
while (await enumerator.MoveNextAsync())
{
Console.WriteLine(enumerator.Current);
}
} This way, the language and libraries like LINQ would only know about the "fast" |
Beta Was this translation helpful? Give feedback.
-
Something like public struct SimpleAsyncEnumerator<T> : IAsyncDisposable
{
private readonly IAsyncEnumerator<T> _enumerator;
public SimpleAsyncEnumerator(IAsyncEnumerator<T> enumerator)
{
_enumerator = enumerator;
Current = default;
}
public async ValueTask<bool> MoveNextAsync()
{
Current = _enumerator.TryGetNext(out bool success);
if(!success)
{
var any = await _enumerator.WaitForNextAsync();
if(!any)
return false;
Current = _enumerator.TryGetNext(out _);
}
return true;
}
public T Current { get; private set;}
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> WaitForNextAsync();
T TryGetNext(out bool success);
} perhaps |
Beta Was this translation helpful? Give feedback.
-
@MadsTorgersen |
Beta Was this translation helpful? Give feedback.
-
@MadsTorgersen public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator();
sealed SinpleAsyncEnumerator<T> GetSimpleAsyncEnumerator() => new SimpleAsyncEnumerator<T>(GetAsyncEnumerator());
}
public struct SimpleAsyncEnumerator<T> : IAsyncDisposable
{
private readonly IAsyncEnumerator<T> _enumerator;
public SimpleAsyncEnumerator(IAsyncEnumerator<T> enumerator)
{
_enumerator = enumerator;
Current = default;
}
public async ValueTask<bool> MoveNextAsync()
{
Current = _enumerator.TryGetNext(out bool success);
if(!success)
{
var any = await _enumerator.WaitForNextAsync();
if(!any)
return false;
Current = _enumerator.TryGetNext(out _);
}
return true;
}
public T Current { get; private set;}
public ValueTask DisposeAsync() => _enumerator.DisposeAsync();
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> WaitForNextAsync();
T TryGetNext(out bool success);
}
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
} Now when defining an AsyncEnumerable you define the faster version, although in most cases I would expect that it would be possible to generate the Enumerator automatically via When consuming the Enumerator, you call GetSimpleAsyncEnumerator. This returns a lightweight struct that wraps the Enumerator, and allows it be consumed in the same way as you would a non-asyncronous Enumerator. However, since this is a struct, not an interface, no extra interface calls are required. It's also possible the JIT would be able to inline the method calls. As such you get all the benefits of the fast Enumerator, whilst still using a pattern that is will be familiar to anyone who has used old style Enumerators. On the other hand, when the next item is not readily available, three interface calls are required. However in such a case, it is likely that an extra interface call is not going to be relevant, as whatever you are awaiting is more likely to be blocking. |
Beta Was this translation helpful? Give feedback.
-
Couldn't the public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> WaitForNextAsync();
T TryGetNext(out bool success);
} to: public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> WaitForNextAsync();
(bool success, T value) TryGetNext();
} making it a little less "problematic"? Or perhaps introduce a proper And if we do that, why can't we then simply change to: public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<(bool ended, T value)> GetOrWaitForNextAsync();
} although that would have some overhead due to |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@ufcpp ah right, and we cannot create a type that could be that either... we have to construct so... hmm right so as it is now that is not possible in C#? Could one think of somehow to allow types to have |
Beta Was this translation helpful? Give feedback.
-
requires CLR changes |
Beta Was this translation helpful? Give feedback.
-
To be able to express covariant/variant on concrete types ( Otherwise, |
Beta Was this translation helpful? Give feedback.
-
Actually, this compiles, but is also very ugly, since it requires the enumerator interface to change 🤣 public interface IAsyncEnumerator<out T, TMaybe>
where TMaybe : IMaybe<T>
{
ValueTask<TMaybe> GetOrWaitForNextAsync();
}
public class SomeAsyncEnumerator<T>
: IAsyncEnumerator<T, Maybe<T>>
{
public ValueTask<Maybe<T>> GetOrWaitForNextAsync()
{
return new ValueTask<Maybe<T>>(new Maybe<T>());
}
}
public interface IMaybe<out T>
{
bool Ended { get; }
T Value { get; }
}
public struct Maybe<T> : IMaybe<T>
{
public Maybe(bool ended, T value)
{
Ended = ended;
Value = value;
}
public bool Ended { get; }
public T Value {get;}
} |
Beta Was this translation helpful? Give feedback.
-
@nietras I'm not sure that actually does what you want. For example, how do you cast |
Beta Was this translation helpful? Give feedback.
-
@svick you are right of course (doh!) this solves nothing, can't use the interface in a covariant way anyway. Too rusty on the covariant stuff, sorry about the spit balling. |
Beta Was this translation helpful? Give feedback.
-
"Faster" is a mantra nowadays in .NET. There are many PRs in CoreClr, Corefx and ASP.NET repos that make the code 5-10% faster. 100% faster is a huge difference. |
Beta Was this translation helpful? Give feedback.
-
When we were looking at the rules for combining nullability in the common-type algorithm (and in type inference) we found one case where it was not associative, and we came up with the following rules to fix that: Unfortunately, these revised rules are not associative. You can see that combining the three values ! ! ? . If you combine from the left you get ~ (with a warning). If you combine from the right you get ! (with a warning). These rules need only one small change to be associative and commutative: Change the two “W~” entries to “W!”. Changing it to "W?" would also work but I think "W!" is a slightly better choice for the same reason that non-null doesn't have a surface syntax in the language (we expect non-null to be more common). I’ve written a program to exhaustively test the proposed revision to prove it is commutative and associative with this change. I’ll update the feature spec shortly. For those trying to follow along, ~ means oblivious, ! means non-null, and ? means possibly null. The table above shows the invariant rule that was proposed in the LDM for combining nullability in the common-type algorithm and in type inference. It is implemented below in The following program that checks commutativity and associativity of the proposed revised rule. using System;
class Program
{
/// <summary>
/// Check that MergeIsNullable is associative by exhaustive testing.
/// </summary>
public static void Main()
{
foreach (VarianceKind variance in Enum.GetValues(typeof(VarianceKind)))
foreach (Nullability a in Enum.GetValues(typeof(Nullability)))
foreach (Nullability b in Enum.GetValues(typeof(Nullability)))
foreach (Nullability c in Enum.GetValues(typeof(Nullability)))
CheckAssociative(variance, a, b, c);
foreach (VarianceKind variance in Enum.GetValues(typeof(VarianceKind)))
foreach (Nullability a in Enum.GetValues(typeof(Nullability)))
foreach (Nullability b in Enum.GetValues(typeof(Nullability)))
CheckCommutative(variance, a, b);
}
private static void CheckCommutative(VarianceKind variance, Nullability a, Nullability b)
{
var result1 = MergeNullability(a, b, variance, out bool mismatch1);
var result2 = MergeNullability(b, a, variance, out bool mismatch2);
if (result1 != result2 || mismatch1 != mismatch2)
{
Console.WriteLine($"Noncommutative Variance.{variance} {a} {b} Result={result1}/{mismatch1} Reversed={result2}/{mismatch2}");
}
}
private static void CheckAssociative(VarianceKind variance, Nullability a, Nullability b, Nullability c)
{
var left = MergeNullability(a, b, variance, out bool mm1);
var result1 = MergeNullability(left, c, variance, out bool mm2);
var mismatch1 = mm1 || mm2;
var right = MergeNullability(b, c, variance, out bool mm3);
var result2 = MergeNullability(a, right, variance, out bool mm4);
var mismatch2 = mm3 || mm4;
if (result1 != result2 || mismatch1 != mismatch2)
{
Console.WriteLine($"Nonassociative Variance.{variance} {a} {b} {c}. LeftResult={result1}/{mismatch1} RightResult={result2}/{mismatch2}");
}
}
private static Nullability MergeNullability(Nullability a, Nullability b, VarianceKind variance, out bool hadNullabilityMismatch)
{
var a1 = nullabilityToBool(a);
var b1 = nullabilityToBool(b);
// var result = MergeIsNullable29727(a1, b1, variance, out hadNullabilityMismatch);
var result = MergeIsNullableProposed(a1, b1, variance, out hadNullabilityMismatch);
return boolToNullability(result);
bool? nullabilityToBool(Nullability x)
{
switch (x)
{
case Nullability.Oblivious: return null;
case Nullability.Nullable: return true;
case Nullability.NonNullable: return false;
default: throw null;
}
}
Nullability boolToNullability(bool? x)
{
switch (x)
{
case null: return Nullability.Oblivious;
case true: return Nullability.Nullable;
case false: return Nullability.NonNullable;
default: throw null;
}
}
}
/// <summary>
/// Merges nullability. Version from PR #29727
/// <paramref name="hadNullabilityMismatch"/> is true if there was conflict.
/// </summary>
private static bool? MergeIsNullable29727(bool? a, bool? b, VarianceKind variance, out bool hadNullabilityMismatch)
{
hadNullabilityMismatch = false;
if (a == b)
{
return a;
}
switch (variance)
{
case VarianceKind.In:
return (a == false || b == false) ? (bool?)false : null;
case VarianceKind.Out:
return (a == true || b == true) ? (bool?)true : null;
default:
if (a == null)
{
return b;
}
if (b == null)
{
return a;
}
hadNullabilityMismatch = true;
return null;
}
}
/// <summary>
/// Merges nullability. Proposed to fix issues with associativity.
/// <paramref name="hadNullabilityMismatch"/> is true if there was conflict.
/// </summary>
private static bool? MergeIsNullableProposed(bool? a, bool? b, VarianceKind variance, out bool hadNullabilityMismatch)
{
hadNullabilityMismatch = false;
if (a == b)
{
return a;
}
switch (variance)
{
case VarianceKind.In:
return (a == false || b == false) ? (bool?)false : null;
case VarianceKind.Out:
return (a == true || b == true) ? (bool?)true : null;
default:
if (a == null)
{
return b;
}
if (b == null)
{
return a;
}
hadNullabilityMismatch = true;
return false; // NOTE THIS LINE CHANGED
}
}
public enum VarianceKind
{
Invariant,
In,
Out,
}
public enum Nullability
{
Oblivious,
Nullable,
NonNullable
}
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
C# Language Design Notes for Oct 1, 2018
C# Language Design Notes for Oct 3, 2018
Beta Was this translation helpful? Give feedback.
All reactions