ufcpp / UfcppSample

http://ufcpp.net/ 向けのサンプル
Apache License 2.0
133 stars 41 forks source link

Collection Expressions #447

Closed ufcpp closed 8 months ago

ufcpp commented 10 months ago

前にブログ書いたやつ https://ufcpp.net/blog/2023/1/collection-literal/ ディクショナリは13行き。

https://ufcpp.net/study/csharp/st_array.html 配列のところに「C# 12でこういう風にも書けるように」みたいな話足す。 「どっち使った方がいい」みたいな質問は出そう。

配列の後ろに新ページで「コレクション」とか要るかも。

もしくは、タプルの話を含めて「初期化」系のページ要るかも。

https://ufcpp.net/study/csharp/datatype/patterns/ パターンマッチングのコレクションパターンのところに「対となる構文」紹介でリンク。

コレクション式は target-typed という話、 https://ufcpp.net/blog/2022/11/covariantarrayincident/ 配列の共変性の事故は防げるはず。

object[] arrayInitializer = new[] { "" }; // 配列初期化子は source-typed/中身からの型推論 → string[]
Console.WriteLine(arrayInitializer.GetType().Name);

object[] collectionExpression = [""]; // コレクション式は target-typed/左辺からの型推論 → object[]
Console.WriteLine(collectionExpression.GetType().Name);
ufcpp commented 9 months ago

コレクションのページ、「データ型」(タプルとかパターンマッチとかあるとこ)にあってもいいかも。

ufcpp commented 9 months ago

target-typed

IEnumerable<(int x, (string[] y, double z)[])> x =
[
    (1, [
        (["a"], 1.1),
        (["a", "ab"], default),
        (["a", "ab", new('a', 1)], new()),
    ]),
    (2, [
        (["b"], 2.1),
        (["b", "bc"], default),
        (["b", "bc", new('b', 2)], new()),
    ]),
];
ufcpp commented 9 months ago

fixer ある。 new[]{} とか new List<T> {} 以外に、Concat とか Append も対応してた。

int[] a = [1, 2, 3];
int[] b = [4, 5];

int[] x = a.Concat(b).ToArray();
// → int[] x = [..a, ..b];

int[] y = a.Append(4).ToArray();
// → int[] x = [..a, 4];

int[] z = a.Concat(b).Append(6).ToArray();
// → int[] x = [..a, ..b, 6];
ufcpp commented 9 months ago

natural type がない(var 相手に使えない)のは一時的な対処(12時点では決めきれなかった)で、13では入る予定。

ufcpp commented 9 months ago

↓今ダメらしい。行けるようにする PR あるっぽい。 ↓ VS 17.8p3 / .NET 8 RC 2 から。

[X([1, 2])]
class A;

class XAttribute : Attribute
{
    public XAttribute(int[] values) { }
}
ufcpp commented 8 months ago

オーバーロード解決がらみ:

A.M1([]);
//A.M2([]);
A.M3([]);
A.M4([]);
//A.M5([]);
A.M6([]);
A.M7([]);
A.M8([]);
A.M9([]);

//B.M(new[] { 1 }); // int[] になっちゃうので、float[] への変換できずにエラー。
B.M([1]); // target-typed なので float[] になる。

new A().M([]); // インスタンス優先。 IEnumerable<int> の方。
new A().M1([]); // 被りがなければ一応拡張メソッド側も見てもらえる。

//[].N(); // こういう拡張メソッド呼びは(今は)無理。
// [].AsList() とか [].AsSpan() とかやりたいという話はある。C# 13 目標。

class B
{
    public static void M(float[] _) { }
}

class A
{
    public static void M1(int[] _) { } // 具象型優先
    public static void M1(IEnumerable<int> _) { }

    public static void M2(int[] _) { } // ambiguous
    public static void M2(List<int> _) { }

    public static void M3(IList<int> _) { }
    public static void M3(List<int> _) { } // 具象型優先

    public static void M4(IList<int> _) { } // 具象型に近い方優先
    public static void M4(IEnumerable<int> _) { }

    public static void M5(IList<int> _) { } // ambiguous
    public static void M5(IReadOnlyList<int> _) { }

    public static void M6(int[] _) { } // 普通は具象型優先。
    public static void M6(Span<int> _) { } // こっちが優先される(コレクション式のみの特殊動作)

    public static void M7(IList<int> _) { } // 普通は 派生 > 型変換
    public static void M7(Span<int> _) { } // こっちが優先される(コレクション式のみの特殊動作)

    public static void M8(IReadOnlyList<int> _) { }
    public static void M8(ReadOnlySpan<int> _) { } // こっちが優先される(コレクション式のみの特殊動作)

    public static void M9(Span<int> _) { } // たぶん GA までに変わる。今こっち。GAでROSの方
    public static void M9(ReadOnlySpan<int> _) { }

    public void M(IEnumerable<int> _) { }
}

static class  AEx
{
    public static void M(this A _, Span<int> _1) { }
    public static void M1(this A _, Span<int> _1) { }

    public static void N(this int[] _) { }
}
ufcpp commented 8 months ago

↓がそこそこいいパフォーマンスになるはず。

static void X1(IEnumerable<int>? a)
{
    // ここ、target-type が IEnumerable なので、 [] は Empty 最適化かかる予定
    foreach (var x in a ?? [])
    {
        Console.WriteLine(x);
    }
}

null coalescing foreach 代わり。

ufcpp commented 8 months ago

内部的に InlineArray 使ったり、ReadOnlyArray (コンパイラー生成)使ったり。 仕様上、実装変えるのは認められてるんで今後もっと効率のいい実装があれば変更の可能性あり。

[] は Array.Empty になったりもする。

ufcpp commented 8 months ago

計画上残ってるけど C# 13 行き:

natural type 迷うのは、

// natural type 何にすべき?
var array = [1, 2, 3];

array.Add(4); // この需要はある = List?

foreach (var x in [1,2,3,4,5,]) // アロケーションかかってほしくない = Span?
{
}

non-generic の、12時点↓はダメ。

using System.Collections;

ICollection c = ["a", 2, null];
ufcpp commented 8 months ago

CollectionBuilder 例えば ImmutableArray とか。 インターフェイスにもつけれる。IImmutableList とか。

CollectionBuilder → コレクション初期化子が使える型はそれと同じ lowering。 配列と、配列が実装してるインターフェイス、Span/ReadOnlySpan は特別扱いしてそう。

実装例:

using System.Collections;
using System.Runtime.CompilerServices;

MyArray<int> array = [1, 2, 3];

foreach (var x in array) Console.WriteLine(x);

[CollectionBuilder(typeof(MyArray), nameof(MyArray.Create))]
struct MyArray<T> : IEnumerable<T>
{
    private readonly T[] _array;
    public MyArray(T[] items) => _array = items;
    public MyArray(IEnumerable<T> items) : this(items.ToArray()) { }
    public MyArray(ReadOnlySpan<T> items) : this(items.ToArray()) { }

    public IEnumerator<T> GetEnumerator() => _array.AsEnumerable().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _array.GetEnumerator();

    public MyArray<T> Add(T item) => new(_array.Append(item));
}

class MyArray
{
    public static MyArray<T> Create<T>(ReadOnlySpan<T> items) => new(items);
}
ufcpp commented 8 months ago

{}new(){}[]

// 配列に関しては {} と [] 同じ。
int[] a1 = { 1, 2, 3 };
int[] a2 = [ 1, 2, 3 ];

List<int> l1 = { 1, 2, 3 }; // ダメ
List<int> l2 = [1, 2, 3]; // OK

var x1 = new X
{
    A = { 1 }, // var x1 = new(); x1.A.Add(1); 扱い(ぬるぽる)。
    // こっちは init なくても平気。

    B = new() { 1 }, // アロケーション2重にかかるのいや
    B = { 1 }, // なので、B みたいなケースではこっちの方がいいはず。
};

var x2 = new X
{
    A = [ 1 ], // x1.A = new() { 1 } 扱い。大丈夫。
};

class X
{
    public List<int> A { get; init; }
    public List<int> B { get; init; } = new();
}
ufcpp commented 8 months ago

属性にもいける。

[A([1, 2, 3, 4, 5])]
class X;

class AAttribute(int[] _) : Attribute;
ufcpp commented 8 months ago

spread (..) concat, append に使える

.. の中身からの型推論は弱め。

byte[] x = [1, 2];

Print([.. x]);     // Print<byte>(byte[])
Print([.. x, 3]);  // Print<int>(int[])

static void Print<T>(T[] args) { }
ufcpp commented 8 months ago

[..a, x, ..b] みたいなの書いたとき、a, b の列挙の評価順序の保証ないみたい。 a, x, b 自体なこの順で評価されるけど、中身の列挙はその後になるかもしれない(遅延評価の時に挙動に保証ない)。

ufcpp commented 8 months ago

書くかどうかは悩ましいけど、null 条件演算子との区別。

bool b = true;
int[] x = [1];

int? y = x?[0];
int[] z = b ? [0] : [1];

コンパイラーが頑張って : を先読みしてるみたい。 Dictionary 式が入ったとき大丈夫かどうか。

ufcpp commented 8 months ago

https://ufcpp.net/study/csharp/datatype/collection-expression/

あとは既存記事からのリンクと、 C# 12 の新機能ページの概要。

ufcpp commented 8 months ago

https://ufcpp.net/study/csharp/st_array.html#key-initializer https://ufcpp.net/study/csharp/sp3_lambda.html?key=collectioninit#collectioninit https://ufcpp.net/study/csharp/cheatsheet/ap_ver12/#collection-expression