Skip to content

Extensions #471

Open
Open

Description

@ufcpp

csharplang 8697
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/extensions.md

配信で取り上げた回
ufcpp-live/UfcppLiveAgenda#102

たぶん、C# 14 だと「プロパティの追加」と「静的メンバーの追加」だけかな。
メソッドも「新文法」に。

↑ その後、拡張 operator は入った。

コンストラクターとかは C# 15 に回ったとのこと。

ExtensionMarkerAttribute とかあるみたいなんだけどどう使ってるのか調べる。

過去

https://ufcpp.net/blog/2024/3/extensions/ (この時代の実装は「やってみたけどダメだった」撤回)

Activity

  1. ufcpp commented on Jun 12, 2024

    @ufcpp
    OwnerAuthor

    昔ちょこっと書いたの:

    要求1: 静的メンバー

    C# 3.0 の拡張メソッドはインスタンス メソッドでだけ「既存の型に追加したように見える書き方」ができます。
    123.Extension() とはできても、int.Extension() とはできません。

    既存の型に静的メソッドを足せなくて困る例としては、ポリフィル(古い環境向けに最新機能を移植して使えるようにするような用途)とかでしょうか。

    例えば割かし最近(.NET 8、2023年11月正式リリース)、TimeProviderという型が入って以下のような書き方ができるようになりました。

    TimeProvider tp = TimeProvider.System;
    
    // 1秒待つ。
    // TimeProvider を差し替えて、単体テストとかでは一瞬で終わるようにしたい。
    await Task.Delay(TimeSpan.FromSeconds(1), tp);
    

    TimeProvider クラス自体は、.NET 8 より前のランタイムでも使えるようにできます。
    (実際、Microsoft.Bcl.TimeProvider という NuGet パッケージが公式提供されています。)
    以下のような書き方で、「なければ追加、あれば type forward」みたいなことができます。

    #if NET8_0_OR_GREATER
    
    using System;
    using System.Runtime.CompilerServices;
    
    [assembly: TypeForwardedTo(typeof(TimeProvider))]
    
    #else
    
    namespace System;
    
    public class TimeProvider
    {
        // 互換実装
    }
    
    #endif
    

    インスタンス メソッドの追加風のことも、拡張メソッドでできました。

    using System.Runtime.CompilerServices;
    
    namespace System.Threading.Tasks;
    
    public static class TaskExtensions
    {
        // GetAwaiter メソッドがあると C# 5.0 の await が使える。
        public static TaskAwaiter GetAwaiter(this Task task)
        {
            // 今はインスタンス メソッドで GetAwaiter があるけど、
            // .NET Framework 4.0 以前はなかったので自作が必要。
            return task.GetAwaiter();
        }
    }
    

    ですが、先ほどの例に出てきた Task.Delay(TimeSpan, TimeProvider) は既存の型への静的メソッドの追加なので、
    これまでの C# ではどうやってもできませんでした。

  2. ufcpp commented on Jul 14, 2024

    @ufcpp
    OwnerAuthor

    13から外れた。
    Unsafe.As 路線は JIT を混乱させて文字通り安全じゃないらしく、急遽路線変更。

  3. ufcpp commented on Oct 27, 2025

    @ufcpp
    OwnerAuthor
    // this 形式の拡張メソッドとの互換性
    //
    // C# 14 で作った extension ブロック形式の拡張メソッドは、
    // C# 13 以前のコンパイラーからも普通に呼べる。
    //
    // 以下のように書いた場合、
    // B 側には A 側の public static void M(this int x) と全く同じメソッドが生成される。
    //
    // this 形式から生成されるメソッドに付いてた [Extension] 属性は、extension 形式から生成されるものにも付く。
    // 一方で、extension 形式特有の「メタデータ用の unspeakable な型」も追加で生成される。
    // 要は、新旧両方のメタデータを生成してる。
    
    static class A
    {
        public static void M(this int x) { }
    
        /* 内部的には以下のようなメソッドと同じ。
    
        [Extension]
        public static void M(int x) { }
         */
    }
    
    static class B
    {
        extension(int x)
        {
            public void M() { }
        }
    
        /* こいつからも以下のようなメソッドは生成されてる。
    
        [Extension]
        public static void M(int x) { }
         */
    }
  4. ufcpp commented on Oct 27, 2025

    @ufcpp
    OwnerAuthor
    static class Ex
    {
        /// <summary>
        /// aaa
        /// </summary>
        /// <param name="s">iii</param>
        public static void M(this string s)
        {
        }
    
        // ここに <param> doc コメント書く想定あるはずだけど、現時点、VS では反映されなさそう。
        // (利用側で "".M1 のヒント見ても s の説明が出ない。)
    
        /// <param name="s">uuu</param>
        extension(string s)
        {
            /// <summary>
            /// eee
            /// </summary>
            public void M1() { }
        }
    }
  5. ufcpp commented on Oct 27, 2025

    @ufcpp
    OwnerAuthor
    // 修飾子とか属性とか盛る
    
    using System.Diagnostics.CodeAnalysis;
    
    class AAttribute : Attribute;
    
    struct S<T1, T2>;
    
    static class Ex
    {
        extension<[A] TKey, TValue>([NotNull][A] scoped ref S<TKey, TValue>? x)
            where TKey : notnull, IComparable<TKey>
            where TValue : unmanaged, IUtf8SpanFormattable
        {
            public static int Q => 1;
        }
    }
  6. ufcpp commented on Oct 27, 2025

    @ufcpp
    OwnerAuthor
    // 残念仕様。
    
    static class Ex
    {
        extension<T>(T x)
        {
            public void M<U>() { }
        }
    
        /*
         * ↓ みたいなコードに展開される。
         * x.M(); で型推論できない。x.M<int, string>(); みたいに2個とも型引数指定必要。
            public static void M<T, U>(this T x) { }
         
         */
    }
  7. ufcpp commented on Oct 27, 2025

    @ufcpp
    OwnerAuthor
    static class A
    {
        extension(int x)
        {
            public void M() { }
        }
    
        extension(int x)
        {
            // 同じ型に対する extension は同一グループ扱いで、名前が被るとコンパイル エラー。
            public void M() { }
        }
    }
    
    static class B
    {
        extension(int x)
        {
            public void M() { }
        }
    
        extension(ref int x)
        {
            // ref/in だけが違うものも同一グループ扱いになる。
            public void M() { }
        }
    
    
        extension(byte x)
        {
            public void M() { } // extension(int).M とは共存可能だけど、
        }
    
        extension(in byte x)
        {
            public void M() { } // これが in byte と byte の共存は不可。
        }
    }
    
    static class C
    {
        // static だと引数を持たなくなるんで別の型でも区別できなくなる。
        extension(int)
        {
            public static void M() { }
        }
    
        extension(string)
        {
            public static void M() { } // コンパイル エラー
        }
    }
  8. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    Ex.get_Property とか Ex.op_Plus とかでも呼べる。

  9. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor
    var e = IEnumerable<string>.M(out object o); // What type is e? Does this line compile?
    
    public static class Ext
    {
        extension<T>(IEnumerable<T>)
        {
            public static IEnumerable<T> M(out T t) => ...;
        }
    }

    object の方になってそう。

  10. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    extension(T) の中に T? の operator 書くのダメらしい。

    S1? s1 = new S1();
    s1 = +s1; // Error: no matching operator
    s1 = +s1.Value; // Ok
    
    public struct S1;
    
    public static class Extensions
    {
        extension(S1)
        {
            public static S1? operator +(S1? x) => x;
        }
    }

    extension の部分がそもそも T? なら大丈夫。

        extension(S1?)
        {
            public static S1? operator +(S1? x) => x;
        }
  11. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor
        // <> ない、引数 x に使わない型 Y があっても平気。
        extension<X, Y>(X x)
        {
    
        }

    特に演算子書くのにこれが必要っぽい。

        extension<X, Y>(X)
        {
            public static X operator +(X x, Y y) => default;
        }

    ちなみに、呼ぶ側:

    int.M(); // エラー。
    
    int.M<int, int>(); // これなら書けちゃう…
    
    static class Accessors
    {
        // <> ない、引数 x に使わない型 Y があっても平気。
        extension<X, Y>(X x)
        {
            public static void M() { }
        }
    }
  12. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    式ツリーの中でだけ使えない extension もあるらしい。
    && と ||。
    対応するファクトリメソッドが Expression 型にないとのこと。

  13. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    extern と UnsafeAccessor も使えるらしい。

    using System.Runtime.CompilerServices;
    
    using var stream = new MemoryStream();
    
    var x = stream._buffer;
    
    Console.WriteLine(x is null);
    
    static class Accessors
    {
        extension(MemoryStream stream)
        {
            [UnsafeAccessor(UnsafeAccessorKind.Field)]
            public extern ref byte[] _buffer();
        }
    
    }
  14. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    in の有無でオーバーロードあり事案:

    int.Static(); // こっちは ambiguous
    
    1.Instance(); // こっちは (int x) の方が選ばれる
    
    static class E1
    {
        extension(int x)
        {
            public static void Static() { }
            public void Instance() { }
        }
    }
    static class E2
    {
        extension(in int i)
        {
            public static void Static() { }
            public void Instance() { }
        }
    }
  15. ufcpp commented on Nov 7, 2025

    @ufcpp
    OwnerAuthor

    readonly 修飾子ダメ。

  16. ufcpp commented on Nov 16, 2025

    @ufcpp
    OwnerAuthor
    // この A と B、全く同じ拡張メソッドのはずなんだけど、A だけエラーになる。
    // (そういう仕様。)
    // 確かに元々 B の形で書けても、呼ぶ側では弁別できなくて拡張メソッドとしては呼べない。
    
    static class A
    {
        extension(int x)
        {
            public void M() { }
        }
    
        extension(ref int x)
        {
            public void M() { }
        }
    }
    
    static class B
    {
        public static void M(this int xx) { }
    
        public static void M(this ref int xx) { }
    }
  17. ufcpp commented on Jan 1, 2026

    @ufcpp
    OwnerAuthor
    static partial class A
    {
        extension(int x)
        {
            // ダメそう。
            public partial int P { get; }
            public partial int P { get => x; }
        }
    
        //public static partial int get_P(int x);
        //public static partial int get_P(int x) => x;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions