web-dev-qa-db-ja.com

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

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

たとえば、コンソールに '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();
    }
}

これはConsoleに 'WriteBlueLine'メソッドを追加しませんでした…私は間違っていますか?それとも不可能を求めて?

493
Leon Bambrick

いいえ。拡張メソッドはオブジェクトのインスタンス変数(値)を必要とします。ただし、ConfigurationManagerインターフェースの周囲に静的ラッパーを記述することはできます。ラッパーを実装する場合は、メソッドを直接追加するだけなので、拡張メソッドは不要です。

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

      .....

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

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; }
}

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

だからここにあなたがこれを使用する方法があります:

    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(...)

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

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

87
Mr. Obnoxious

不可能です。

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

彼らの決定は理にかなっていないし、プログラマに無意味なラッパークラスを書くことを強いる。

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

これを行う唯一の方法は、さまざまなクラスを指すか、数百ものさまざまなAssertメソッドのラッパーを作成することです。 なぜだ!?

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

44
Tom Deloford

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

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

そして私はこのように使います:

ConsoleColor.Cyan.WriteLine("voilà");
18
Adel G.Eibesh

カスタムの名前空間と同じクラス名で静的クラスを追加することもできます。

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);
        }
    }
}
17
Pag Sun

いや。拡張メソッドの定義には、拡張している型のインスタンスが必要です。残念です。なぜそれが必要なのかわからない….

10
Will

C#7では、これはサポートされていません。しかし、 C#8にそのようなものを統合することについての議論サポートする価値のある提案 があります。

9
mbx

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

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
7
Brian Griffin

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

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

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

5
Brannon

私が拡張メソッドを学んでいて成功しなかったときに私は戻ってSystem.Environmentでこれをやろうとしました。その理由は、他の人が言うように、拡張メソッドはクラスのインスタンスを必要とするからです。

4
Robert S.

拡張メソッドを書くことはできませんが、あなたが求めている振る舞いをまねることは可能です。

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)を呼び出すことができます。他のクラスがConsoleの他の静的関数にアクセスしたい場合、それらはそれらの名前空間を通して明示的に参照されなければならないでしょう。

すべてのメソッドを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
Douglas Potesta

はい、限られた意味でです。

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

これは動作しますが、Consoleは静的であるために動作しません。

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に何かをフックすることです。それは行数や悪いWordフィルタ、あるいは何でもあり得ます。名前空間にConsoleと指定してWebProject1と入力し、System名前空間をインポートすると、System.Consoleの代わりにWebProject1.Consoleが名前空間WebProject1のクラスのデフォルトとして選択されます。そのため、このコードでは、System.Console.WriteLineを指定していない限り、すべてのConsole.WriteLine呼び出しが青色に変わります。

1
Black Dog

以下は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);
    }
}
1

それを機能させるためにnullのキャストを使うことができます。

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

拡張子:

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

あなたのタイプ:

public class YourType { }
0
Wouter