기존 정적 클래스에 확장 메소드를 추가 할 수 있습니까?


533

C #에서 확장 메서드를 좋아하지만 콘솔과 같은 정적 클래스에 확장 메서드를 추가하는 데 성공하지 못했습니다.

예를 들어 콘솔에 'WriteBlueLine'이라는 확장을 추가하고 싶다면 다음을 수행하십시오.

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

콘솔을 'this'매개 변수로 사용하여 로컬 공개 정적 메소드를 추가하여 시도했지만 주사위는 없습니다!

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

이것은 콘솔에 'WriteBlueLine'메서드를 추가하지 않았습니다 ... 잘못하고 있습니까? 아니면 불가능을 요구합니까?


3
오 잘 불행히도 나는 내가 갈 것이라고 생각합니다. 나는 여전히 확장 코드 처녀입니다 (어쨌든 프로덕션 코드에서). 운이 좋으면 언젠가는
Andy McCluggage

ASP.NET MVC에 대한 여러 HtmlHelper 확장을 작성했습니다. DateTime에 대해 하나를 작성하여 주어진 날짜의 끝을 제공했습니다 (23 : 59.59). 사용자에게 종료 날짜를 지정하도록 요청했지만 실제로 그 날짜가 끝나기를 원할 때 유용합니다.
tvanfosson

12
C #에 기능이 없으므로 현재 추가 할 수 없습니다. 그 자체로 는 불가능 하기 때문에가 아니라 C # 엿보기가 매우 바쁘기 때문에 LINQ를 작동시키는 확장 방법에 주로 관심이 있었고 정적 확장 방법에서 구현 시간을 정당화하기에 충분한 이점을 얻지 못했습니다. 에릭 리퍼 트는 여기서 설명한다 .
Jordan Gray

1
그냥 전화 Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

답변:


285

아니요. 확장 메서드에는 개체의 인스턴스 변수 (값)가 필요합니다. 그러나 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

ConfigurationSection을 구현하는 클래스에 정적 메소드를 추가하는 것이 더 도움이된다는 것을 알게되었습니다. 따라서 MyConfigurationSection이라는 구현이 제공되면 MyConfigurationSection.GetSection ()을 호출하여 이미 입력 된 섹션을 반환하거나 존재하지 않는 경우 null을 반환합니다. 최종 결과는 동일하지만 클래스 추가를 피합니다.
탭하십시오

1
@tap-이것은 단지 예일 뿐이며 가장 먼저 떠오른 것입니다. 그러나 단일 책임 원칙이 적용됩니다. "컨테이너"가 실제로 구성 파일에서 자신을 해석해야합니까? 일반적으로 ConfigurationSectionHandler가 있고 ConfigurationManager의 출력을 적절한 클래스로 캐스팅하고 래퍼를 신경 쓰지 않습니다.
tvanfosson

5
사내 사용을 위해 사용자 정의 확장을 추가하기위한 정적 클래스 및 구조의 'X'변형을 만들기 시작했습니다. 'Color'메서드 등을 확장합니다. 동일하지는 않지만 IntelliSense에서 쉽게 기억하고 발견 할 수 있습니다.
user1689175

1
@Xtro 나는 끔찍하지만, 테스트 클래스를 두 번 사용할 수 없다는 것보다 나쁘지 않다는 것에 동의합니다. 또는 정적 클래스가 너무 어려워 코드 테스트를 포기하지 않습니다. MVC에 대한 정적 HttpContext.Current를 해결하기 위해 HttpContextWrapper / HttpContextBase 클래스를 도입 한 이유 때문에 Microsoft는 저에게 동의하는 것 같습니다.
tvanfosson 2016 년

91

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 가비지를 모두 수행하지 않고 첫 번째 코드 스 니펫에서 새 T ()를 반환하지 않습니까? 오늘은 운이 좋은 날인데 2fer를 얻었 기 때문입니다. 고급 .NET 개발자가 알고 있듯이 새 T ()는 리플렉션을 사용하여 기본 생성자를 가져와 호출하기 전에 System.Activator에 대한 호출을 생성하므로 속도가 느립니다. 젠장, 마이크로 소프트! 그러나 내 코드는 객체의 기본 생성자를 직접 호출합니다.

정적 확장은 이것보다 낫지 만 절박한 시간에는 필사적 인 조치가 필요합니다.


2
Dataset에 대해서는이 방법이 효과가 있다고 생각하지만 Console이 정적 클래스이므로 정적 클래스를 인수로 사용할 수 없으므로 Console 클래스에서 작동한다고 의심됩니다.
ThomasBecker

예, 같은 말을하려고 했어요 비 정적 클래스의 의사 정적 확장 메소드입니다. OP는 정적 클래스의 확장 방법이었습니다.
Mark A. Donohoe

2
그냥 같은 그러한 방법에 대한 몇 가지 명명 규칙이 훨씬 더 쉽게로이다 XConsole, ConsoleHelper등등을.
Alex Zhukovskiy

9
이것은 매혹적인 트릭이지만 결과는 냄새가납니다. "널 (null) 객체에서 메소드를 호출하면 예외가 발생한다"는 말에도 불구하고 널 (null) 객체를 생성 한 다음 메소드를 호출하는 것처럼 보입니다. 그것은 작동하지만 ..ugh ... 나중에 유지 관리하는 사람에게 혼란. 가능한 정보에 대해 정보 풀에 추가했기 때문에 공감하지 않습니다. 그러나 나는 아무도이 기술을 사용하지 않기를 진심으로 바란다! 추가 불만 사항 : 이들 중 하나를 메소드에 전달하지 말고 OO 서브 클래스 화를 기대하십시오. 호출 된 메소드는 전달 된 매개 변수 유형이 아닌 매개 변수 선언 유형입니다 .
ToolmakerSteve

5
까다 롭지 만 마음에 듭니다. 대안이 (null as DataSet).Create();될 수 있습니다 default(DataSet).Create();.
Bagerfahrer

54

불가능합니다.

그리고 네, MS는 여기서 실수를 한 것 같습니다.

그들의 결정은 의미가 없으며 프로그래머가 (위에서 설명한 것처럼) 무의미한 래퍼 클래스를 작성하도록 강요합니다.

다음은 좋은 예입니다. 정적 MS 단위 테스트 클래스를 확장하려고 시도합니다. Assert : 1 개 이상의 Assert 메소드가 필요 AreEqual(x1,x2)합니다.

이를 수행 할 수있는 유일한 방법은 다른 클래스를 가리 키거나 약 100 개의 다른 Assert 메소드를 래퍼로 작성하는 것입니다. 왜!?

인스턴스 확장을 허용하기로 결정한 경우 정적 확장을 허용하지 않는 논리적 인 이유가 없습니다. 인스턴스를 확장 할 수 있으면 라이브러리 섹션화에 대한 논쟁은 일어나지 않습니다.


20
또한 Assert.Throws 및 Assert.DoesNotThrow를 추가하기 위해 MS Unit Test 클래스 Assert를 확장하려고 시도했지만 동일한 문제에 직면했습니다.
Stefano Ricciardi 8:24에

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 ()를 호출하여 System.Activator.CreateInstance ()를 호출하기 위해 IL "call"명령으로 컴파일합니다. 제네릭 형식에 대해 사용될 경우 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 # 컴파일러에게 .정적 / 확장 메서드의 첫 번째 매개 변수로 왼쪽의 인스턴스를 전달하도록 지시 하는 것입니다.

정적 메소드를 유형에 추가하는 경우 첫 번째 매개 변수에 전달할 인스턴스가 없습니다.


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)을 호출 할 수 있습니다. 다른 클래스가 콘솔의 다른 정적 함수에 액세스하려면 네임 스페이스를 통해 명시 적으로 참조해야합니다.

모든 메소드를 한 곳에 두려면 항상 모든 메소드를 대체 클래스에 추가 할 수 있습니다.

그래서 당신은 같은 것을 가질 것입니다

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이라는 네임 스페이스를 가져올 때마다 WebProject1.Console은 System.Console보다 네임 스페이스 WebProject1의 해당 클래스에 대해 기본값으로 선택됩니다. 따라서이 코드는 System.Console.WriteLine을 지정하지 않은 한 모든 Console.WriteLine 호출을 파란색으로 바꿉니다.


기본 클래스는 (은 .NET 클래스 라이브러리의 많은처럼) 밀봉 후손의 doen't 작업 사용의 불행하게도 접근
조지 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)에 할당하여 조금 "frig"하려는 경우이 작업을 수행 할 수 있습니다. 그러나이 메소드는 클래스의 정적 호출에 사용할 수 없으므로 얼마나 많이 사용되는지 확실하지 않습니다.

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 객체를 숨길 수 있다고 생각합니다. 감사.
Tom Deloford

1
이 코드를 컴파일 할 수 없습니다. 오류 'System.Console': 정적 형식을 매개 변수로 사용할 수 없습니다
kuncevic.dev

예,이 작업을 수행 할 수 없습니다. 젠장, 네가 뭔가 있다고 생각 했어! 정적 유형은 매개 변수로 메소드에 전달할 수 없습니다. MS 가이 나무에서 나무를보고 변경하기를 바랍니다.
Tom Deloford

3
내 자신의 코드를 컴파일하려고했습니다! Tom이 말했듯이 정적 클래스에서는 작동하지 않습니다.
Tenaka
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.