Syntax Suggestions for C# 8 Await Foreach and Extensions #840
Replies: 42 comments
-
Could you share a link to the video, please? It helps those of us who haven't seem it and puts context to your suggestions. |
Beta Was this translation helpful? Give feedback.
-
https://channel9.msdn.com/Blogs/Seth-Juarez/A-Preview-of-C-8-with-Mads-Torgersen |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Maybe |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@mariusGundersen Is it me, or is JavaScript creeping into C#? |
Beta Was this translation helpful? Give feedback.
-
@siegfriedpammer, as Mads said in the video, they are stealing ideas from TypeScript, which is a superset of EcmaScript, so I guess ideas are creeping in both directions. |
Beta Was this translation helpful? Give feedback.
-
@mariusGundersen I am fine with ideas and features, but I don't think C# should follow the example of JavaScript in terms of syntax. |
Beta Was this translation helpful? Give feedback.
-
Just because the semantics for
I take it back, 😆 |
Beta Was this translation helpful? Give feedback.
-
@eyalsk For the same reason, IMHO, the syntax should be one of
|
Beta Was this translation helpful? Give feedback.
-
Is an async foreach e.g. could you do this? Task t = foreach await (var i in stream) ....
// ...
await t; |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Adding an cosmetic equals for variable assignment
Or adding a intermediate task var that is returned from the for
I still think 4. |
Beta Was this translation helpful? Give feedback.
-
@benaadams But it is maybe the best syntax variant to avoid any misunderstandings about whats going on. The |
Beta Was this translation helpful? Give feedback.
-
@MillKaDe I assume there is an implicit await on the foreach otherwise the options are kinda
As you should be able to capture the overall sequence in a
|
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi Can the naming be optional and a name will be auto-generated by convention when no name given? Something like- //Name auto-generated to StringExtension or something like that
extension String
{
...
}
//Name not generated
static class MyStringExtension extension String
{
...
} The if any conflict happens with convention based auto generated names, an error will be shown. |
Beta Was this translation helpful? Give feedback.
-
If you let the compiler generate a name, then it's always an "unspeakable" name in order to avoid conflicts. I'd rather not change that behavior, since otherwise that would need an exact specification on how to generate a name and you're marrying yourself to that spec for the rest of time. |
Beta Was this translation helpful? Give feedback.
-
I've seen that the proposed design of the interfaces is like this: public interface IAsyncEnumerable<T>
{
public IAsyncEnumerator<T> GetEnumerator();
}
public interface IAsyncEnumerator<T>
{
public T Current { get; }
public Task<bool> MoveNextAsync();
} I believe it is MUCH better to change the IAsuncEnumerator to this: public interface IAsyncEnumerator<T>: IDisposable
{
public Task<Optional<T>> MoveNextAsync();
} And assume the the None value is returned at the end. Task<Something> MyOperationAsync(obj)
{
var x = await DoSomethingSlowAsync(obj);
await WriteToDBAsync(x);
return new Something(obj, x);
}
Task<...> MySecondOperationAsync(...)
{
///...
}
var results = await GetStreamOfObjectFromRemoteService()
.Select(o => MyOperationAsync(o))
.Select(o => MySecondOperationAsync(o))
.Parallelize(maxDegreeOfParallelizm: 20, maxPrefetchedItemsQueueSize: 1000) // this parallelizes everything above
.ToListAsync(); Assuming that not only the MySecondOperationAsync is parallelized. I mean this should work the same as var results = await GetStreamOfObjectFromRemoteService()
.Select(o =>
{
var a = await MyOperationAsync(o);
return await MySecondOperationAsync(a);
})
.Parallelize(maxDegreeOfParallelizm: 20, maxPrefetchedItemsQueueSize: 1000)
.ToListAsync(); So each time you call MoveNextAsync() you potentially start a new task. Of course the enumerators generated by using async yield returns should put semaphore.WaitAsync() to prevent from parallelization. But the methods like Select should allow executing the continuations in parallel if the client called MoveNextAsync multiple times not awaiting the previous calls results. Btw, I think the Linq methods can be implemented directly for the IAsyncEnumerator. |
Beta Was this translation helpful? Give feedback.
-
IIRC the problem with defining the interface like that is the loss of covariance. |
Beta Was this translation helpful? Give feedback.
-
While we're at it, how about this? public interface IAsyncEnumerator<T>: IDisposable
{
public Task<IReadOnlyList<T>> MoveNextAsync();
} Allows for batching. Empty list signals end of async enumeration. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour |
Beta Was this translation helpful? Give feedback.
-
@jnm2 Yes, that is possible solution. But it would be more difficult to work with batches every time. |
Beta Was this translation helpful? Give feedback.
-
For the conversation around Extensions syntax, I don't know why we wouldn't try to limit the introduction of keywords. The addition of Current syntax for extensions on a reference type... public static class PersonExtensionClass
{
public string FullName(this Person person)
{
return $"{person.FirstName} {person.MiddleName} {person.LastName}";
}
} Current proposed syntax for extensions on a reference type... public extension PersonExtensionClass extends Person
{
public string FullName()
{
return $"{this.FirstName} {this.MiddleName} {this.LastName}";
}
} My proposed syntax for extensions on a reference type is mainly around the class declaration itself, not the logic being suggested within the class definition itself. My main gripe is that the proposal is for extensions to become their own classes in itself, as they would also maintain their own state, aren't following the syntax of normal classes. If extensions should be able to extend other extensions, there is the requirement to inheir like normal classes. public extension PersonExtensionClass<IPerson>
{
// code
}
public extension PersonExtensionClassOfExtensionClass<IPerson> : PersonExtensionClass<IPerson>
{
// code
}
public extension PersonExtensionClassOfExtensionClass<IPerson> : PersonExtensionClass<IPerson>, IDispose
{
// code
} Seems a bit more natural in this usage to me. |
Beta Was this translation helpful? Give feedback.
-
The latter. It waits for at least one item and returns all the items it has at that time without waiting for more.
Like I said, empty list signals end of async enumeration. I think |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
@HaloFour public extension IAsyncEnumeratorExtensions<TDerived, TBase> where TDerived: TBase for IAsyncEnumerator<TDerived>: IAsyncEnumerator<TBase>
{
public Task<Optional<TBase>> MoveNextAsync()
{
Optional<TDerived> item = await this.MoveNextAsync();
return item.IsNone ? Optional<TBase>.None : new Optional<TBase>(item.Value);
}
} So even Object.ReferenceEquals would return true. |
Beta Was this translation helpful? Give feedback.
-
I wanted to add to the conversation regarding the "extension everything" syntax. Current syntax adds two new keywords:
I think we really only need one. The public class Foo extends Bar
{
// ... extension everything stuff
} It's really a tiny change, but avoids two new keywords when one will do. Now, wherever you see |
Beta Was this translation helpful? Give feedback.
-
I would suggest the following design for IAsyncEnumerator: public interface IAsyncEnumerator<out T> : IDisposable // covariant
{
ValueTask<ResultToken?> MoveNextAsync();
T FetchResult(ResultToken resultToken);
}
public struct ResultToken
{
internal readonly object source;
internal readonly long key;
internal ResultToken(object source, long key)
{
this.source = source;
this.key = key;
}
}
public static class AsyncEnumerator
{
public static async ValueTask<Optional<T>> FetchNextAsync<T>(this IAsyncEnumerator<T> source)
{
ResultToken? token = await source.MoveNextAsync().ConfigureAwait(false);
return token.HasValue ? source.FetchResult(token.Value) : Optional<T>.None;
}
}
public struct Optional<T>
{
public static readonly Optional<T> None = default(Optional<T>);
private readonly bool hasValue;
private readonly T value;
public Optional(T value)
{
this.value = value;
this.hasValue = true;
}
public bool HasValue => this.hasValue;
public T Value => this.hasValue ? this.value : throw new InvalidOperationException("Doesn't have value");
public static implicit operator Optional<T>(T value) => new Optional<T>(value);
}
internal abstract class AsyncEnumeratorBase<T> : IAsyncEnumerator<T>
{
private readonly ConcurrentDictionary<long, T> results = new ConcurrentDictionary<long, T>();
private long lastKey;
public async ValueTask<ResultToken?> MoveNextAsync()
{
Optional<T> result = await this.FetchNextAsync().ConfigureAwait(false); // virtual method
if (!result.HasValue)
{
return null;
}
long key = Interlocked.Increment(ref this.lastKey);
this.results[key] = result.Value;
return new ResultToken(this, key);
}
public T FetchResult(ResultToken resultToken)
{
if (!ReferenceEquals(resultToken.source, this))
{
throw new ArgumentException("Token from different source");
}
return this.results.TryRemove(resultToken.key, out T result)
? result
: throw new InvalidOperationException("Has been already fetched");
}
protected abstract ValueTask<Optional<T>> FetchNextAsync();
public abstract void Dispose();
}
//Sample async enumerator
internal sealed class WhereAsyncEnumerator<T> : AsyncEnumeratorBase<T>
{
private readonly IAsyncEnumerator<T> source;
private readonly Func<T, bool> predicate;
public WhereAsyncEnumerator(IAsyncEnumerator<T> source, Func<T, bool> predicate)
{
this.source = source;
this.predicate = predicate;
}
protected override async ValueTask<Optional<T>> FetchNextAsync()
{
while (true)
{
Optional<T> result = await this.source.FetchNextAsync().ConfigureAwait(false); // extension method
if (!result.HasValue || this.predicate(result.Value))
{
return result;
}
}
}
public override void Dispose()
{
source.Dispose();
}
} |
Beta Was this translation helpful? Give feedback.
-
@artelk that design looks impractical. You can look at AsyncEnumerable library - the implementation can be more complex than that. |
Beta Was this translation helpful? Give feedback.
-
I'm guessing this can be closed as C# shipped with |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I just watched the video on Channel 9 about the new C# 8 features. I really like the new features!
I hope I am not too late, but there are just two things I would like to propose:
foreach await (var s in stream)
looks a bit strange. How aboutforeach (await var s in stream)
? This would make it more C#-like as we have only one keyword before the open parenthesis in all the other control structures.extension A extends ClassA : ...
is very similar to inheritance in other languages (Java), which might be confusing for other people. Plus, it's a new keyword. I would propose to useextension A for ClassA : ...
.Benefits include:
a) Reuse of existing keyword.
b) Similar to natural language.
Beta Was this translation helpful? Give feedback.
All reactions