既存の静的クラスに拡張メソッドを追加できますか?


535

私はC#の拡張メソッドのファンですが、拡張メソッドをコンソールなどの静的クラスに追加することに成功していません。

たとえば、「WriteBlueLine」と呼ばれる拡張機能をコンソールに追加したい場合は、次のようにします。

Console.WriteBlueLine("This text is blue");

私はこれを、「this」パラメーターとしてConsoleを使用してローカルのパブリック静的メソッドを追加することで試してみました...

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

これはコンソールに「WriteBlueLine」メソッドを追加しませんでした...私はそれを間違っていますか?または不可能を求めますか?


3
しかたがない。残念ですが、うまくいくと思います。私はまだ拡張メソッドvirgin(とにかくプロダクションコード)です。運が良ければ、たぶんある日。
Andy McCluggage 2008年

ASP.NET MVCのHtmlHelper拡張機能をいくつか作成しました。与えられた日付の終わり(23:59.59)を与えるために、DateTimeに1つ書き込んだ。ユーザーに終了日を指定するように依頼したが、実際にはその日の終了にしたい場合に役立ちます。
tvanfosson 2008年

12
この機能はC#に存在しないため、現在これらを追加する方法はありません。それ自体が不可能ではないためではありませんが、C#ののぞき見は非常に忙しいため、LINQを機能させるための拡張メソッドに主に関心があり、実装にかかる時間を正当化するための静的拡張メソッドには十分な利点がありませんでした。エリックリッパートはここで説明します
ジョーダングレイ、

1
ただ、コールHelpers.WriteBlueLine(null, "Hi");:)
フセインYağlı

回答:


286

いいえ。拡張メソッドには、オブジェクトのインスタンス変数(値)が必要です。ただし、ConfigurationManagerインターフェースの静的ラッパーを作成することはできます。ラッパーを実装する場合は、メソッドを直接追加できるため、拡張メソッドは必要ありません。

 public static class ConfigurationManagerWrapper
 {
      public static ConfigurationSection GetSection( string name )
      {
         return ConfigurationManager.GetSection( name );
      }

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }

8
@Luis-コンテキストでは、「特定のセクションを取得するために、ConfigurationManagerクラスに拡張メソッドを追加できますか?」オブジェクトのインスタンスが必要なため、静的クラスに拡張メソッドを追加することはできませんが、同じシグネチャを実装し、実際のConfigurationManagerへの実際の呼び出しを延期するラッパークラス(またはファサード)を作成できます。必要なメソッドをラッパークラスに追加できるので、拡張機能である必要はありません。
tvanfosson 2010

ConfigurationSectionを実装するクラスに静的メソッドを追加する方が便利だと思います。したがって、MyConfigurationSectionと呼ばれる実装が与えられたら、MyConfigurationSection.GetSection()を呼び出します。これは、すでに入力されたセクションを返すか、セクションが存在しない場合はnullを返します。最終結果は同じですが、クラスの追加は回避されます。
タップ

1
@tap-これは単なる例であり、最初に思いついたものです。ただし、単一責任の原則が機能します。「コンテナ」は実際に構成ファイルからそれ自体を解釈する責任がありますか?通常、私は単にConfigurationSectionHandlerを持ち、ConfigurationManagerからの出力を適切なクラスにキャストし、ラッパーを気にしません。
tvanfosson

5
社内で使用するために、カスタム拡張を追加するための静的クラスと構造の「X」バリアントの作成を開始しました。「ConsoleX」には「Console」の新しい静的メソッドが含まれ、「MathX」には「Math」、「ColorX」の新しい静的メソッドが含まれます'Color'メソッドなどを拡張します。まったく同じではありませんが、IntelliSenseで覚えて発見するのは簡単です。
user1689175 2014

1
@Xtro私はそれがひどいことに同意しますが、その場所でテストdoubleを使用できないことよりも悪くはありません。あるいは、静的クラスは非常に難しいため、コードのテストをあきらめます。MVCの静的なHttpContext.Currentを回避するためにHttpContextWrapper / HttpContextBaseクラスを導入したのは、Microsoftが私に同意しているようです。
tvanfosson 2017年

92

C#でクラスに静的拡張機能を追加できますか?いいえ、あなたはこれを行うことができます:

public static class Extensions
{
    public static T Create<T>(this T @this)
        where T : class, new()
    {
        return Utility<T>.Create();
    }
}

public static class Utility<T>
    where T : class, new()
{
    static Utility()
    {
        Create = Expression.Lambda<Func<T>>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))).Compile();
    }
    public static Func<T> Create { get; private set; }
}

これがどのように機能するかです。静的な拡張メソッドを技術的に書くことはできませんが、代わりにこのコードは拡張メソッドの抜け穴を利用します。その抜け穴は、@ thisを介して何かにアクセスしない限り、null例外を取得せずにnullオブジェクトの拡張メソッドを呼び出すことができることです。

したがって、これは次のように使用します。

    var ds1 = (null as DataSet).Create(); // as oppose to DataSet.Create()
    // or
    DataSet ds2 = null;
    ds2 = ds2.Create();

    // using some of the techniques above you could have this:
    (null as Console).WriteBlueLine(...); // as oppose to Console.WriteBlueLine(...)

では、なぜデフォルトのコンストラクターを呼び出すことを例に選んだのか、そしてその式のガベージをすべて実行せずに、最初のコードスニペットでnew T()を返さないのはなぜですか?まあ今日はあなたが2ferを手に入れるのであなたの幸運な日です。高度な.NET開発者が知っているように、new T()は、リフレクションを使用してデフォルトコンストラクターを取得してから呼び出す前にSystem.Activatorへの呼び出しを生成するため、処理が遅くなります。くそーマイクロソフト!しかし、私のコードはオブジェクトのデフォルトのコンストラクターを直接呼び出します。

静的な拡張はこれよりも優れていますが、絶望的な時期には必死の対策が必要です。


2
データセットではこのトリックはうまくいくと思いますが、コンソールは静的クラスなので、静的クラスは引数として使用できないため、コンソールクラスではうまく機能しないと
思い

ええ、私は同じことを言うつもりでした。これは、非静的クラスの疑似静的拡張メソッドです。OPは静的クラスの拡張メソッドでした。
Mark A. Donohoe、2015

2
それだけのような、そのような方法のためのいくつかの命名規則を持つことがより良いと簡単だXConsoleConsoleHelperとそうで。
Alex Zhukovskiy 2017年

9
これは魅力的なトリックですが、結果は臭いです。「nullオブジェクトでメソッドを呼び出すと例外が発生する」と何年も言われているにもかかわらず、nullオブジェクトを作成し、その上でメソッドを呼び出しているように見えます。機能しますが、後で...メンテナンスしている人を混乱させます。あなたが何ができるかについての情報のプールに追加したので、私は反対投票しません。でも、誰もこのテクニックを使ってくれないことを願っています!追加の不満:これらの1つをメソッドに渡さないでください。OOサブクラス化が行われることを期待してください。呼び出されるメソッドは、渡されパラメーターのタイプではなく、パラメーター宣言のタイプになります
ToolmakerSteve

5
これはトリッキーですが、私はそれが好きです。一つの代替には(null as DataSet).Create();なり得ますdefault(DataSet).Create();
Bagerfahrer

54

不可能です。

そしてはい、私はMSがここで間違いを犯したと思います。

彼らの決定は意味をなさず、プログラマーに(上記のように)無意味なラッパークラスを書くように強制します。

ここに良い例があります:静的なMSユニットテストクラスを拡張しようとしますAssert:さらに1つのAssertメソッドが必要AreEqual(x1,x2)です。

これを行う唯一の方法は、異なるクラスを指すか、数百の異なるAssertメソッドのラッパーを記述することです。なぜ!?

インスタンスの拡張を許可することが決定された場合、静的な拡張を許可しない論理的な理由はありません。ライブラリをセクション化することに関する議論は、インスタンスを拡張できるようになると成り立たなくなります。


20
また、MSユニットテストクラスAssertを拡張してAssert.ThrowsとAssert.DoesNotThrowを追加しようとしても、同じ問題に直面していました。
Stefano Ricciardi

3
うん、私はあまりに:(私は私が行うことができます思ったAssert.Throws回答にstackoverflow.com/questions/113395/...
CallMeLaNN

27

OPが持っていたのと同じ質問に対する答えを見つけようとしているときに、私はこのスレッドに出くわしました。欲しい答えは見つかりませんでしたが、結局これをやりました。

public static class MyConsole
{
    public static void WriteLine(this ConsoleColor Color, string Text)
    {
        Console.ForegroundColor = Color;
        Console.WriteLine(Text);   
    }
}

そして私はそれを次のように使用します:

ConsoleColor.Cyan.WriteLine("voilà");

19

たぶん、カスタム名前空間と同じクラス名で静的クラスを追加できます:

using CLRConsole = System.Console;

namespace ExtensionMethodsDemo
{
    public static class Console
    {
        public static void WriteLine(string value)
        {
            CLRConsole.WriteLine(value);
        }

        public static void WriteBlueLine(string value)
        {
            System.ConsoleColor currentColor = CLRConsole.ForegroundColor;

            CLRConsole.ForegroundColor = System.ConsoleColor.Blue;
            CLRConsole.WriteLine(value);

            CLRConsole.ForegroundColor = currentColor;
        }

        public static System.ConsoleKeyInfo ReadKey(bool intercept)
        {
            return CLRConsole.ReadKey(intercept);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteBlueLine("This text is blue");   
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
        }
    }
}

1
しかし、これはラッパーに保持したい元の静的クラスからすべての単一のメソッドを再実装する必要があるという問題を解決しません。それはまだラッパーですが、それを使用するコードの変更が少なくて済むというメリットがあります…
binki


11

いいえ。拡張メソッドの定義には、拡張する型のインスタンスが必要です。その不幸です。なぜ必要なのかわかりません...


4
これは、オブジェクトのインスタンスを拡張するために拡張メソッドが使用されるためです。そうしなければ、通常の静的メソッドになります。
Derek Ekins、

31
両方を行うのはいいことですよね。

7

拡張メソッドについては、拡張メソッド自体は静的です。ただし、インスタンスメソッドであるかのように呼び出されます。静的クラスはインスタンス化できないため、拡張メソッドを呼び出すクラスのインスタンスはありません。このため、コンパイラーは、静的クラスに対して拡張メソッドを定義することを許可していません。

Obnoxious氏は、「上級の.NET開発者なら誰でも知っているように、新しいT()は、リフレクションを使用してデフォルトのコンストラクターを取得する前にリフレクションを使用するSystem.Activatorへの呼び出しを生成するため、遅い」と述べています。

コンパイル時に型がわかっている場合、New()はILの「newobj」命令にコンパイルされます。Newobjは、直接呼び出し用のコンストラクターを取ります。System.Activator.CreateInstance()を呼び出すと、ILの「call」命令にコンパイルされ、System.Activator.CreateInstance()が呼び出されます。New()をジェネリック型に対して使用すると、System.Activator.CreateInstance()が呼び出されます。この点に関して、Obnoxious氏の投稿は不明確でした...そして、まあ、不愉快です。

このコード:

System.Collections.ArrayList _al = new System.Collections.ArrayList();
System.Collections.ArrayList _al2 = (System.Collections.ArrayList)System.Activator.CreateInstance(typeof(System.Collections.ArrayList));

このILを生成します。

  .locals init ([0] class [mscorlib]System.Collections.ArrayList _al,
           [1] class [mscorlib]System.Collections.ArrayList _al2)
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldtoken    [mscorlib]System.Collections.ArrayList
  IL_000c:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0011:  call       object [mscorlib]System.Activator::CreateInstance(class [mscorlib]System.Type)
  IL_0016:  castclass  [mscorlib]System.Collections.ArrayList
  IL_001b:  stloc.1

5

静的を追加することはできません型にメソッドを。タイプのインスタンスに追加できるのは(疑似)インスタンスメソッドのみです。

this修飾子のポイントは、C#コンパイラにインスタンスの左側にインスタンスを渡すように指示することです。. static / extensionメソッドの最初のパラメータとしてことです。

静的メソッドを型に追加する場合、最初のパラメーターに渡すインスタンスはありません。


4

拡張メソッドを学んでいたときに、System.Environmentを使用してこれを試みましたが、成功しませんでした。その理由は、他の人が述べているように、拡張メソッドにはクラスのインスタンスが必要だからです。


3

拡張メソッドを作成することはできませんが、求めている動作を模倣することは可能です。

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
}

これにより、他のクラスでConsole.WriteBlueLine(fooText)を呼び出すことができます。他のクラスがコンソールの他の静的関数にアクセスする場合は、それらの名前空間を介して明示的に参照する必要があります。

すべてのメソッドを1つの場所に配置したい場合は、いつでもすべてのメソッドを置換クラスに追加できます。

だからあなたは次のようなものになるでしょう

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
    public static void WriteLine(string text)
    {
        FooConsole.WriteLine(text);
    }
...etc.
}

これはあなたが探しているような行動を提供します。

*注:コンソールは、配置したネームスペースを介して追加する必要があります。


1

はい、限られた意味で。

public class DataSet : System.Data.DataSet
{
    public static void SpecialMethod() { }
}

これは機能しますが、コンソールは静的であるため機能しません。

public static class Console
{       
    public static void WriteLine(String x)
    { System.Console.WriteLine(x); }

    public static void WriteBlueLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.Write(.x);           
    }
}

これは、同じ名前空間上にない限り機能します。問題は、System.Consoleが持つすべてのメソッドに対してプロキシ静的メソッドを作成する必要があることです。次のようなものを追加できるので、必ずしも悪いことではありません。

    public static void WriteLine(String x)
    { System.Console.WriteLine(x.Replace("Fck","****")); }

または

 public static void WriteLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.WriteLine(x); 
    }

それが機能する方法は、標準のWriteLineに何かをフックすることです。これは、行数や不適切な単語フィルターなどです。名前空間でConsoleを指定してWebProject1と指定し、名前空間Systemをインポートすると、名前空間WebProject1のこれらのクラスのデフォルトとして、System.ConsoleよりもWebProject1.Consoleが選択されます。したがって、このコードは、System.Console.WriteLineを指定したことがない限り、すべてのConsole.WriteLine呼び出しを青に変えます。


残念ながら、(。NETクラスライブラリの多くのように)基本クラスがシールされている場合、子孫を使用するアプローチは機能しません
George Birbilis

1

以下は、tvanfossonの回答の編集として拒否されました。自分の答えとして寄稿をお願いされました。私は彼の提案を使用して、ConfigurationManagerラッパーの実装を完了しました。原則として、私...はtvanfossonの答えを記入しました。

いいえ。拡張メソッドにはオブジェクトのインスタンスが必要です。ただし、ConfigurationManagerインターフェースの静的ラッパーを作成できます。ラッパーを実装する場合は、メソッドを直接追加するだけなので、拡張メソッドは必要ありません。

public static class ConfigurationManagerWrapper
{
    public static NameValueCollection AppSettings
    {
        get { return ConfigurationManager.AppSettings; }
    }

    public static ConnectionStringSettingsCollection ConnectionStrings
    {
        get { return ConfigurationManager.ConnectionStrings; }
    }

    public static object GetSection(string sectionName)
    {
        return ConfigurationManager.GetSection(sectionName);
    }

    public static Configuration OpenExeConfiguration(string exePath)
    {
        return ConfigurationManager.OpenExeConfiguration(exePath);
    }

    public static Configuration OpenMachineConfiguration()
    {
        return ConfigurationManager.OpenMachineConfiguration();
    }

    public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel)
    {
        return ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel);
    }

    public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap)
    {
        return ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
    }

    public static void RefreshSection(string sectionName)
    {
        ConfigurationManager.RefreshSection(sectionName);
    }
}

0

nullのキャストを使用して機能させることができます。

public static class YoutTypeExtensionExample
{
    public static void Example()
    {
        ((YourType)null).ExtensionMethod();
    }
}

拡張子:

public static class YourTypeExtension
{
    public static void ExtensionMethod(this YourType x) { }
}

YourType:

public class YourType { }

-4

静的クラスの変数を作成してそれをnullに割り当てることにより、少しでも「フリッグ」するつもりであれば、これを行うことができます。ただし、このメソッドはクラスの静的呼び出しでは使用できないため、どの程度使用されるかわかりません。

Console myConsole = null;
myConsole.WriteBlueLine("my blue line");

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

これはまさに私がやったことです。私のクラスは:) MYTRACEと呼ばれている
Gishu

便利なヒント。コードのにおいがしますが、nullオブジェクトを基本クラスなどに隠すことができると思います。ありがとう。
トムデロフォード

1
このコードをコンパイルできません。エラー 'System.Console':静的型はパラメーターとして使用できません
kuncevic.dev

はい、できません。くそー私はあなたがそこに何かがあると思った!静的型をパラメーターとしてメソッドに渡すことはできません。MSがこの木にある木を見て、それを変更することを望んでみましょう。
トムデロフォード

3
私は自分のコードをコンパイルしようとしたはずです!トムが言うように、これは静的クラスでは機能しません。
テナカ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.