Steve Love
@IAmSteveLove@mastodon.social
@stevelove.bsky.social
Steve Love
@IAmSteveLove@mastodon.social
@stevelove.bsky.social
Who are you?
I hope you learn something interesting!
I want to mess with your mind! (and I hope you still learn something interesting)
Basic arithmetic
Every problem becomes very childish when once it is explained to you.
double price = 200.0;
double percent = 20.0;
price *= 1 + percent / 100.0;
What does this do?
For a binary operator op, a compound assignment expression of the form
x op= y
is equivalent to
x = x op y
except that x is only evaluated once.
double a = 200.0;
double b = 0.2;
a = a * b;
Assert.That(a, Is.EqualTo(40));
double a = 200.0;
double b = 0.2;
a *= b;
Assert.That(a, Is.EqualTo(40));
double price = 200.0;
double percent = 20.0;
price *= 1 + percent / 100.0;
(a) |
|
|
(b) |
|
|
double price = 200.0;
double percent = 20.0;
price *= 1 + percent / 100.0;
Assert.That(price, Is.EqualTo(240));
(a) |
|
|
But…why?
Operators | Category or name |
blah… | blah… |
| Multiplicative |
| Additive |
blah… | blah… |
| Assignment and lambda declaration |
|
|
| 240.0 |
It matters!
It is a capital mistake to theorize before one has data.
|
|
var child = new Child();
Console.WriteLine(child.Name);
a)
| b)
| c)
|
|
|
var child = new Child();
Console.WriteLine(child.Name);
a)
| b)
| c) |
![]() |
|
var child = new Child();
Console.WriteLine(child.Name);
a)
| b)
| c)
|
![]() |
|
var child = new Child();
Console.WriteLine(child.Name);
a)
| b) | c)
|
With a (virtual) set
accessor:
IL_0013: ldarg.0 // this
IL_0014: ldarg.1 // name
IL_0015: callvirt instance void code.Test_auto_mutable_prop/Base::set_Name(string)
IL_001a: nop
Without a set
accessor:
IL_0013: ldarg.0 // this
IL_0014: ldarg.1 // name
IL_0015: stfld string code.Test_auto_immutable_prop/Base::'<Name>k__BackingField'
but it’s just minutiae, right?
I never make exceptions. An exception disproves the rule.
…return value
|
|
Your program might exit unconditionally
i.e. it’ll crash
probably
Warning: high-level hand waving here!
A function that returns a future
A a result that may be obtained on a different thread asynchronously, and made available to the current thread by a promise
asynchronous exceptions are…tricksy
An exception is a result!
See 1
async
doesn’t make a method asynchronousactually, await
is where any asynchrony happens
The Synchronization Context decides
The future is Task
public async Task PersistState() // <-- async Task
{
await Task.Run(() =>
{
// long-running operation
// ... that may fail
throw new Exception("An arbitrary error");
});
}
But…
the exception is still not caught in the caller
async
all the way downpublic async void Flush(object? state) // <-- async
{
try
{
await PersistState(); // <-- await
Console.WriteLine("Done");
}
catch // anything
{
Console.WriteLine("Error");
}
}
But…we’re back to async void
!
There is nothing more deceptive than an obvious fact.
required
public readonly struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public DateOnly DOB { get; init; }
}
var person = new Person
{
FirstName = "John",
LastName = "Doe"
};
public readonly struct Person
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required DateOnly DOB { get; init; }
}
var person = new Person
{
FirstName = "John",
LastName = "Doe"
};
Error CS9035 : Required member 'Person.DOB' must be set in the object initializer or attribute constructor.
var person = new Person
{
FirstName = "John",
LastName = default,
DOB = default
};
[CS8625] Cannot convert null literal to non-nullable reference type.
…but silent on the default DOB
Person person = default;
Consider making this a sin! |
public readonly struct Person
{
public Person(string firstName, string lastName, DateOnly dob)
=> (FirstName, LastName, DOB) = (firstName, lastName, dob);
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required DateOnly DOB { get; init; }
}
var person = new Person("John", "Doe", new DateOnly(2010, 12, 31));
[CS9035] Required member 'Person.FirstName' must be set in the object initializer or attribute constructor.
public readonly struct Person
{
[SetsRequiredMembers]
public Person(string firstName, string lastName, DateOnly dob)
=> (FirstName, LastName, DOB) = (firstName, lastName, dob);
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required DateOnly DOB { get; init; }
public required string Pronoun { get; init; }
}
var person = new Person("John", "Doe", new DateOnly(2010, 12, 31));
Should this be ok?
public readonly struct Person
{
public Person(string firstName, string lastName, DateOnly dob, string pronoun)
=> (FirstName, LastName, DOB, Pronoun) = (firstName, lastName, dob, pronoun);
public string FirstName { get; }
public string LastName { get; }
public DateOnly DOB { get; }
public string Pronoun { get; }
}
var person = new Person { FirstName = "John", LastName = "Doe" };
[CS0200] Property or indexer 'Test_construct.Person.FirstName' cannot be assigned to -- it is read only
We must look for consistency. Where there is a want of it we must suspect deception.
public class Converter(char from)
{
public static explicit operator Converter(char value)
=> new(value);
}
decimal money = 24.5m + 11.5m;
var result = (Converter)money;
Does this compile?
Should it?
decimal money = 24.5m + 11.5m;
var result = (Converter)money;
is equivalent to this:
var result = Converter.op_Explicit(money);
public class Converter(char from)
{
public static explicit operator Converter(char value)
=> new(value);
public static Converter From(char value)
=> new(value);
}
var result = Converter.From(money);
var result = Converter.From((char)money);
decimal money = 24.5m + 11.5m;
var result = (Converter)money;
IL_0012: call char System.Decimal::op_Explicit(valuetype System.Decimal)
IL_0017: call class Converter Converter::op_Explicit(char)
First, if required, performing a standard conversion from the source expression to the operand type of the user-defined or lifted conversion operator.
Next, invoking the user-defined or lifted conversion operator to perform the conversion.
Finally, if required, performing a standard conversion from the result type of the user-defined conversion operator to the target type.
It is, of course, a trifle, but there is nothing so important as trifles.