ufcpp-live / UfcppLiveAgenda

@ufcpp live streaming agenda
MIT License
24 stars 2 forks source link

C# 10.0 構造体がらみ #43

Closed ufcpp closed 2 years ago

ufcpp commented 2 years ago

配信URL: https://youtu.be/gmFukCxLGc4

主に、record struct

record struct Point(int X, int Y);

と引数なしコンストラクター

record struct A();

struct B
{
    public int[] Buffer = new int[4];
}

struct C
{
    public int[] Buffer;
    public C() : this(4) { }
    public C(int length) => Buffer = new int[length];
}

ufcpp.net 向けにサンプルコード書き始めてる:

https://github.com/ufcpp/UfcppSample/tree/master/Demo/2021/Csharp10/RecordStruct https://github.com/ufcpp/UfcppSample/tree/master/Demo/2021/Csharp10/ParameterlessConstructors

記事書きトラッキング issue:

https://github.com/ufcpp/UfcppSample/issues/343 https://github.com/ufcpp/UfcppSample/issues/353

の話。

たぶん、default と Activator の深淵に落ちる。

ufcpp-live commented 2 years ago
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

var a = new A { X = 0 };

Unsafe.As<A, int>(ref a) = 0x01020304;

Console.WriteLine(a.X);

Console.WriteLine(a.Equals(new A { X = 4 }));

// SkipLocalsInit とか P/Invoke とかがある以上、
// 使ってない「残り3バイト」を無視できない。
// なので、構造体の Equals は思ったよりも「単なる memcmp」じゃない。
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct A
{
    public byte X;
}
ufcpp-live commented 2 years ago
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

var a = new A();
var b = new A();

// 使ってるビットだけをちゃんと比較してくれる。
// これの場合、最下位桁の 04。
Unsafe.As<A, int>(ref a) = 0x01020304;
Unsafe.As<A, int>(ref b) = 0x05060604;

Console.WriteLine(a.Equals(b));

[StructLayout(LayoutKind.Sequential, Pack = 4)]
record struct A(byte X);
ufcpp-live commented 2 years ago
using System.Runtime.CompilerServices;

var a = new B();
var b = new B();

// 隙間3バイトはちゃんと無視される。
Unsafe.As<B, ulong>(ref a) = 0x1234567801020304;
Unsafe.As<B, ulong>(ref b) = 0x1234567805060604;

Console.WriteLine(a.Equals(b));

record struct A(byte X); // 1バイト
record struct B(A X, int Y); // 8バイトで、XとYの間に3バイト隙間あり
ufcpp-live commented 2 years ago
Console.WriteLine(new Point1(1, 2)); // ToString からは X は消える。
Console.WriteLine(new Point1(1, 2).Equals(new(3, 2))); // Equals には private field も関与

record struct Point1(int X, int Y)
{
    private int X = X;
    public int Y = Y;
}
ufcpp-live commented 2 years ago
using System;
var anonymous = new { X = 1, Y = 2 };
Console.WriteLine(anonymous with { X = 3 }); // ildasm したらコンストラクター呼び出しに置き換わってた。
ufcpp-live commented 2 years ago
record struct A(int X); // これ、get; set; なので net48 とかでもコンパイル可能

// ↓この2つは自前定義の IsExternalInit が必要になる
record B(int X);
readonly record struct C(int X);

//namespace System.Runtime.CompilerServices
//{
//    public class IsExternalInit { }
//}
ufcpp-live commented 2 years ago
using System;

var t = (X: 1, Y: 2) with { X = 3 };

// そもそも 1-tuple の (X: 1) がダメ。
// with は無関係。
var t1 = (X: 1) with { X = 3 };

#if false
const int N = 1;
class N { }
(N) // 既存コードではキャストの意味なので、今更 1-tuple にできない。
#endif

var t2 = ValueTuple.Create(1) with { Item1 = 2 }; // 行ける
ufcpp-live commented 2 years ago
void x = null; // これは null すらアウト、元がない

Unit x = null; // これはどんなに new を禁止しても null の1元はある

class Unit
{
    private Unit() { }
}
ufcpp-live commented 2 years ago
// このコードは net48/C# 7 として合法。
// そこから LangVersion 10 で netstandard2.0 なライブラリを参照すると…
using System;

class Program
{
    static void Main()
    {
        // ジェネリックな new() は、内部的には CreateInstance<T>() と一緒
        Console.WriteLine(New<ClassLibrary1.A>().X); // 1
        Console.WriteLine(New<ClassLibrary1.A>().X); // 0
        Console.WriteLine(New<ClassLibrary1.A>().X); // 0
        Console.WriteLine(New<ClassLibrary1.A>().X); // 0
    }

    static T New<T>()
        where T : new()
        => new T();
}
ufcpp-live commented 2 years ago
var x = new PositiveInt();
Console.WriteLine(x.Value); // 1

x = default;
Console.WriteLine(x.Value); // 0

// 書けるのであれば時々書きたいことはなくはない。
struct PositiveInt
{
    public int Value;

    public PositiveInt() : this(1) { }

    public PositiveInt(int x)
    {
        if (x <= 0) throw new ArgumentException(nameof(x));
        Value = 1;
    }
}
ufcpp-live commented 2 years ago
// default != new() なことを有用に使える場面はなくはない。

var array = new PositiveInt[] { new(1), new(2) };
var first = array.FirstOrDefault(x => x.Value > 3); // あえて条件満たさない

if (!first.IsDefault)
{
    Console.WriteLine(first.Value);
}
else
{
    Console.WriteLine("見つからなかったよ");
}

struct PositiveInt
{
    public bool IsDefault => Value == 0;

    public int Value;

    public PositiveInt() : this(1) { }

    public PositiveInt(int x)
    {
        if (x <= 0) throw new ArgumentException(nameof(x));
        Value = 1;
    }
}
ufcpp-live commented 2 years ago

実はすでに not-default 判定ロジックを持ってる。

image

ufcpp-live commented 2 years ago

non-defaultable 構造体が欲しい…

var x = NotNull<int?>(); // これはこの行が警告になることで not-null
var y = Defaultable<int?>(); // null

Console.WriteLine(y == null);

var z = Defaultable<int>(); // T? といいつつ not-null。0。defaultable。
Console.WriteLine(z == null); // false

T NotNull<T>() where T : notnull, new() => new();
T? Defaultable<T>() => default;