A session for .Net Conf TLV 30.10.2019 (Tel-Aviv, Israel).
The session describes most of the new features that were added to C# 8.0 and .Net Core 3.
Sample Code can be found here: https://github.com/MoaidHathot/.NET-Conf-TLV-2019
18. Motivation
• Enable an API author to add methods to an interface in future versions without breaking source
or binary compatibility.
• Enables C# to interoperate with APIs targeting Android (Java) and iOS (Swift).
• Provides the elements of “traits”.
33. Usage
public async IAsyncEnumerable<int> GetRandomNumbersAsync(int count)
{
var random = new Random();
for (var index = 0; index < count; ++index)
{
var number = random.Next(500, 4001);
await Task.Delay(number);
yield return number;
}
}
48. Index
public readonly struct Index : IEquatable<Index>
{
public int Value { get; } //Actual index
public bool IsFromEnd { get; }
public Index(int value, bool fromEnd = false)
public static Index Start { get; } //Return new Index(0) – Private API
public static Index End { get; } //Return new Index(-1) – Private API
public int GetOffset(int length);
public static Index FromStart(int value);
public static Index FromEnd(int value);
public static implicit operator Index(int value);
}
49. Range
public readonly struct Range : IEquatable<Range>
{
public Index Start { get; }
public Index End { get; }
public Range(Index start, Index end);
public static Range All { get; } //Compiler Magic
public static Range StartAt(Index start);
public static Range EndAt(Index start);
public (int offset, int length) GetOffsetAndLength(int length)
50. Type support
• Both Index and Range
• Array
• String
• Span<T>
• ReadOnlySpan<T>
• Indices only
• List<T>
51. Implicit support
• The compiler provides implicit support for Index and Range
• Given some conditions
• Using Duck Typing
52. Countable types
• A type is countable if it has one of the following properties
• Length
• Accessible and returns an int
• Count
• Accessible and returns an int
• If both Count and Length exists, the compiler will use Length
53. Implicit support for Index
• Conditions
• The type is Countable
• The type has an accessible instance indexer with a single int argument
• The type does not have an accessible instance indexer with Index as the first parameter
• Index must be the only parameter or the remaining parameters must be optional
• What is generated
• For indices of the form n
• It will be translated to Index.GetOffset(n) and used with the available indexer;
• For indices of the form ^n
• It will be translated to Index.GetOffset(obj.Length – n) and used with the available
indexer
54. Implicit support for Range
• Conditions
• The type is Countable
• The type has an accessible member named Slice accepting two int parameters
• The type does not have an instance indexer with Range as the first parameter
• Range must be the only parameter or the remaining parameters must be optional
• What is generated
• It has more use cases than Index but the result is similar
• The Range will be translated into a call to the Slice member.
55. Index
void Main()
{
var test = new Test(10);
test[^1].Dump();
test[new Index(4)].Dump();
}
class Test
{
public int Count { get; }
public Test(int maximum)
{
Count = maximum;
}
public int this[int index] => index;
}
56. Index
void Main()
{
var test = new Test(10);
test[^1].Dump();
test[new Index(4)].Dump();
}
class Test
{
public int Count { get; }
public Test(int maximum)
{
Count = maximum;
}
public IEnumerable<int> Slice(int start, int end) => Enumerable.Range(start, end - start);
}
62. Using declaration
void Test()
{
using var foo1 = new Foo();
using var foo2 = new Foo();
}
class Foo : IDisposable
{
public void Dispose()
{
"Disposed".Dump();
}
}
63. Using declaration
async Task Test()
{
await using var foo1 = new Foo();
await using var foo2 = new Foo();
}
class Foo : IDisposable
{
public void Dispose()
{
"Disposed".Dump();
}
}
64. Using declaration
Foo foo = new Foo();
try
{
foo.Dump();
}
finally
{
if (foo != null)
{
((IDisposable)foo).Dispose();
}
}