Tôi có thể thêm các phương pháp mở rộng cho một lớp tĩnh hiện hành?


534

Tôi là người hâm mộ các phương thức mở rộng trong C #, nhưng chưa có thành công nào khi thêm phương thức mở rộng vào lớp tĩnh, chẳng hạn như Bảng điều khiển.

Ví dụ: nếu tôi muốn thêm tiện ích mở rộng vào Bảng điều khiển, được gọi là 'WriteBlueLine', để tôi có thể đi:

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

Tôi đã thử điều này bằng cách thêm một phương thức tĩnh cục bộ, công khai, với Console là tham số 'this' ... nhưng không có xúc xắc!

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

Điều này đã không thêm phương thức 'WriteBlueLine' vào Bảng điều khiển ... tôi có đang làm sai không? Hoặc yêu cầu những điều không thể?


3
Ồ tốt không may nhưng tôi nghĩ tôi sẽ nhận được. Tôi vẫn VẪN là một phương thức mở rộng (trong mã sản xuất nào). Có lẽ một ngày, nếu tôi may mắn.
Andy McClADE

Tôi đã viết một số phần mở rộng HtmlHelper cho ASP.NET MVC. Đã viết một cho DateTime để cho tôi kết thúc ngày đã cho (23: 59,59). Hữu ích khi bạn yêu cầu người dùng chỉ định ngày kết thúc, nhưng thực sự muốn nó là ngày kết thúc của ngày đó.
tvanfosson

12
Hiện tại không có cách nào để thêm chúng vì tính năng này không tồn tại trong C #. Không phải vì nó không thể cho mỗi gia nhập , nhưng vì C # peeps rất bận rộn, được chủ yếu quan tâm đến phương pháp khuyến nông để làm cho công việc LINQ và không thấy đủ lợi ích trong phương pháp khuyến nông tĩnh để biện minh cho thời gian họ sẽ làm để thực hiện. Eric Lippert giải thích ở đây .
Jordan Gray

1
Chỉ cần gọi Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

Câu trả lời:


285

Không. Các phương thức mở rộng yêu cầu một biến đối tượng (giá trị) cho một đối tượng. Tuy nhiên, bạn có thể viết một trình bao bọc tĩnh xung quanh ConfigurationManagergiao diện. Nếu bạn triển khai trình bao bọc, bạn không cần một phương thức mở rộng vì bạn chỉ có thể thêm phương thức trực tiếp.

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

      .....

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

8
@Luis - trong ngữ cảnh, ý tưởng sẽ là "tôi có thể thêm một phương thức mở rộng vào lớp Cấu hình để có được một phần cụ thể không?" Bạn không thể thêm một phương pháp mở rộng cho một lớp tĩnh vì nó đòi hỏi một thể hiện của đối tượng, nhưng bạn có thể viết một lớp wrapper (hoặc mặt tiền) mà cụ cùng một chữ ký và trì hoãn các cuộc gọi thực tế để các ConfigurationManager thực. Bạn có thể thêm bất kỳ phương thức nào bạn muốn vào lớp trình bao bọc để nó không cần phải là một phần mở rộng.
tvanfosson

Tôi thấy hữu ích hơn khi chỉ cần thêm một phương thức tĩnh vào lớp triển khai Cấu hình. Vì vậy, cho một thực hiện gọi MyConfigurationSection, tôi sẽ gọi MyConfigurationSection.GetSection (), trả về phần đã đánh máy, hoặc null nếu nó không tồn tại. Kết quả cuối cùng là như nhau, nhưng nó tránh thêm một lớp.
chạm vào

1
@tap - đó chỉ là một ví dụ và là ví dụ đầu tiên xuất hiện trong đầu. Các nguyên tắc trách nhiệm duy nhất đi vào chơi, mặc dù. "Container" có thực sự chịu trách nhiệm phiên dịch chính nó từ tệp cấu hình không? Thông thường tôi chỉ đơn giản là có Cấu hình cấu hình và chuyển đầu ra từ Trình quản lý cấu hình sang lớp thích hợp và không bận tâm với trình bao bọc.
tvanfosson

5
Để sử dụng trong nhà, tôi bắt đầu tạo 'X' biến thể của các lớp học và các cấu trúc tĩnh để thêm Extensions tùy chỉnh: 'ConsoleX' chứa các phương pháp tĩnh mới cho 'điều khiển', 'MathX' chứa các phương pháp tĩnh mới cho 'Toán', 'ColorX' mở rộng các phương thức 'Màu', v.v. Không hoàn toàn giống nhau, nhưng dễ nhớ và khám phá trong IntelliSense.
dùng1689175

1
@Xtro Tôi đồng ý rằng điều đó thật tồi tệ, nhưng không tệ hơn là không thể sử dụng một bài kiểm tra kép ở vị trí của nó hoặc tệ hơn là từ bỏ việc kiểm tra mã của bạn vì các lớp tĩnh làm cho nó rất khó. Microsoft dường như đồng ý với tôi vì đó là lý do họ giới thiệu các lớp HttpContextWrapper / HttpContextBase để đi xung quanh tĩnh httpContext.C hiện tại cho MVC.
tvanfosson

91

Bạn có thể thêm các phần mở rộng tĩnh cho các lớp trong C # không? Không nhưng bạn có thể làm điều này:

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

Đây là cách nó hoạt động. Mặc dù về mặt kỹ thuật, bạn không thể viết các phương thức mở rộng tĩnh, thay vào đó, mã này khai thác lỗ hổng trong các phương thức mở rộng. Lỗ hổng đó là bạn có thể gọi các phương thức mở rộng trên các đối tượng null mà không nhận được ngoại lệ null (trừ khi bạn truy cập bất cứ thứ gì qua @this).

Vì vậy, đây là cách bạn sẽ sử dụng này:

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

Bây giờ TẠI SAO tôi đã chọn gọi hàm tạo mặc định làm ví dụ và VÀ tại sao tôi không trả lại T () mới trong đoạn mã đầu tiên mà không thực hiện tất cả rác biểu thức đó? Vâng, ngày hôm nay là ngày may mắn của bạn vì bạn nhận được 2fer. Như bất kỳ nhà phát triển .NET nâng cao nào cũng biết, T () mới chậm vì nó tạo ra một cuộc gọi đến System.Activator sử dụng sự phản chiếu để có được hàm tạo mặc định trước khi gọi nó. Chết tiệt bạn Microsoft! Tuy nhiên, mã của tôi gọi hàm tạo mặc định của đối tượng trực tiếp.

mở rộng tĩnh sẽ tốt hơn thế này nhưng thời gian tuyệt vọng kêu gọi các biện pháp tuyệt vọng.


2
Tôi nghĩ rằng đối với Dataset, thủ thuật này sẽ hoạt động, nhưng tôi nghi ngờ rằng nó hoạt động với lớp Console vì Console là lớp tĩnh, các kiểu tĩnh không thể được sử dụng làm đối số :)
ThomasBecker

Vâng, tôi sẽ nói điều tương tự. Đây là phương thức mở rộng giả tĩnh trên một lớp không tĩnh. OP là một phương thức mở rộng trên một lớp tĩnh.
Đánh dấu A. Donohoe

2
Sẽ tốt hơn và dễ dàng hơn khi chỉ có một số quy ước đặt tên cho các phương thức như vậy XConsole, ConsoleHelperv.v.
Alex Zhukovskiy

9
Đây là một mẹo hấp dẫn, nhưng kết quả là có mùi. Bạn tạo một đối tượng null, sau đó xuất hiện để gọi một phương thức trên nó - mặc dù nhiều năm được nói rằng "gọi một phương thức trên một đối tượng null gây ra một ngoại lệ". Nó hoạt động, nhưng ..ugh ... Khó hiểu với bất cứ ai duy trì sau này. Tôi sẽ không downvote, vì bạn đã thêm vào nhóm thông tin như những gì có thể. Nhưng tôi chân thành hy vọng không ai từng sử dụng kỹ thuật này !! Khiếu nại bổ sung: Không chuyển một trong số này cho một phương thức và mong muốn nhận được phân lớp OO: phương thức được gọi sẽ là loại khai báo tham số không phải là loại tham số được truyền vào .
ToolmakerSteve

5
Điều này là khó khăn, nhưng tôi thích nó. Một thay thế (null as DataSet).Create();có thể được default(DataSet).Create();.
Bagerfahrer

54

Điều đó là không thể.

Và vâng tôi nghĩ MS đã phạm sai lầm ở đây.

Quyết định của họ không có ý nghĩa và buộc các lập trình viên phải viết (như được mô tả ở trên) một lớp bao bọc vô nghĩa.

Đây là một ví dụ điển hình: Cố gắng mở rộng lớp thử nghiệm Đơn vị MS tĩnh Khẳng định: Tôi muốn thêm 1 phương thức Khẳng định AreEqual(x1,x2).

Cách duy nhất để làm điều này là trỏ đến các lớp khác nhau hoặc viết một trình bao bọc khoảng 100 phương thức Khẳng định khác nhau. Tại sao!?

Nếu quyết định được đưa ra để cho phép các phần mở rộng của phiên bản, tôi thấy không có lý do hợp lý nào để không cho phép các phần mở rộng tĩnh. Các đối số về các thư viện phân đoạn không đứng lên một khi các thể hiện có thể được mở rộng.


20
Tôi cũng đã cố gắng mở rộng lớp Kiểm tra đơn vị MS Assert để thêm Assert.Throws và Assert.DoesNotThrow và đối mặt với cùng một vấn đề.
Stefano Ricciardi

3
Vâng tôi cũng vậy :( Tôi nghĩ tôi có thể làm gì Assert.Throwsđể trả lời stackoverflow.com/questions/113395/
Kẻ

27

Tôi tình cờ tìm thấy chủ đề này trong khi cố gắng tìm câu trả lời cho cùng một câu hỏi mà OP có. Tôi đã không tìm thấy câu trả lời mình muốn nhưng cuối cùng tôi đã làm điều này.

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

Và tôi sử dụng nó như thế này:

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

19

Có lẽ bạn có thể thêm một lớp tĩnh với không gian tên tùy chỉnh và cùng tên lớp:

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
Nhưng điều này không giải quyết được vấn đề cần phải thực hiện lại mọi phương thức từ lớp tĩnh ban đầu mà bạn muốn giữ trong trình bao bọc của mình. Nó vẫn là một trình bao bọc, mặc dù nó có giá trị là cần ít thay đổi hơn trong mã sử dụng nó
đó là


11

Không. Mở rộng định nghĩa phương pháp đòi hỏi một thể hiện của kiểu bạn đang mở rộng. Nó không may; Tôi không chắc tại sao lại cần ...


4
Đó là bởi vì một phương thức mở rộng được sử dụng để mở rộng một thể hiện của một đối tượng. Nếu họ không làm điều đó thì họ sẽ chỉ là các phương thức tĩnh thông thường.
Derek Ekins

31
Thật tuyệt khi làm cả hai, phải không?

7

Đối với phương pháp khuyến nông, phương pháp khuyến nông chính họ là tĩnh; nhưng chúng được gọi như thể chúng là các phương thức thể hiện. Vì một lớp tĩnh là không thể thực hiện được, nên bạn sẽ không bao giờ có một thể hiện của lớp để gọi một phương thức mở rộng từ đó. Vì lý do này, trình biên dịch không cho phép các phương thức mở rộng được định nghĩa cho các lớp tĩnh.

Ông Obnoxious đã viết: "Như bất kỳ nhà phát triển .NET nâng cao nào cũng biết, T () mới chậm vì nó tạo ra một cuộc gọi đến System.Activator sử dụng sự phản chiếu để có được hàm tạo mặc định trước khi gọi nó".

New () được biên dịch theo hướng dẫn "newobj" của IL nếu loại được biết tại thời điểm biên dịch. Newobj có một hàm tạo để gọi trực tiếp. Các lệnh gọi tới System.Activator.CreateInstance () biên dịch theo lệnh "gọi" IL để gọi System.Activator.CreateInstance (). Mới () khi được sử dụng đối với các loại chung sẽ dẫn đến lệnh gọi System.Activator.CreateInstance (). Bài đăng của ông Obnoxious không rõ ràng về điểm này ... và tốt, đáng ghét.

Mã này:

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

sản xuất IL này:

  .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

Bạn không thể thêm tĩnh phương thức vào một loại. Bạn chỉ có thể thêm các phương thức cá thể (giả cho) vào một thể hiện của một loại.

Điểm của công cụ thissửa đổi là yêu cầu trình biên dịch C # vượt qua thể hiện ở phía bên trái của. tham số đầu tiên làm phương thức tĩnh / mở rộng.

Trong trường hợp thêm các phương thức tĩnh vào một kiểu, không có trường hợp nào chuyển cho tham số đầu tiên.


4

Tôi đã thử làm điều này với System.EnFE trở lại khi tôi đang học các phương pháp mở rộng và không thành công. Lý do là, như những người khác đề cập, bởi vì các phương thức mở rộng yêu cầu một thể hiện của lớp.


3

Không thể viết một phương thức mở rộng, tuy nhiên có thể bắt chước hành vi mà bạn đang yêu cầu.

using FooConsole = System.Console;

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

Điều này sẽ cho phép bạn gọi Console.WriteBlueLine (fooText) trong các lớp khác. Nếu các lớp khác muốn truy cập vào các hàm tĩnh khác của Console, chúng sẽ phải được tham chiếu rõ ràng thông qua không gian tên của chúng.

Bạn luôn có thể thêm tất cả các phương thức vào lớp thay thế nếu bạn muốn có tất cả chúng ở một nơi.

Vì vậy, bạn sẽ có một cái gì đó như

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

Điều này sẽ cung cấp loại hành vi bạn đang tìm kiếm.

* Note Console sẽ phải được thêm vào thông qua không gian tên mà bạn đặt nó vào.


1

vâng, trong một ý nghĩa hạn chế.

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

Điều này hoạt động nhưng Console không vì nó tĩnh.

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

Điều này hoạt động vì miễn là nó không nằm trên cùng một không gian tên. Vấn đề là bạn phải viết một phương thức tĩnh proxy cho mọi phương thức mà System.Console có. Nó không hẳn là một điều xấu vì bạn có thể thêm một cái gì đó như thế này:

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

hoặc là

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

Cách thức hoạt động là bạn móc thứ gì đó vào WriteLine tiêu chuẩn. Nó có thể là một dòng đếm hoặc bộ lọc từ xấu hoặc bất cứ điều gì. Bất cứ khi nào bạn chỉ định Bảng điều khiển trong không gian tên của bạn nói WebProject1 và nhập Hệ thống không gian tên, WebProject1.Console sẽ được chọn trên System.Console làm mặc định cho các lớp đó trong không gian tên WebProject1. Vì vậy, mã này sẽ biến tất cả các cuộc gọi Console.WriteLine thành màu xanh trong khi bạn chưa bao giờ chỉ định System.Console.WriteLine.


Thật không may, cách tiếp cận sử dụng hậu duệ không hoạt động khi lớp cơ sở bị niêm phong (giống như nhiều người trong thư viện lớp .NET)
George Birbilis

1

Sau đây đã bị từ chối như là một chỉnh sửa cho câu trả lời của tvanfosson. Tôi đã được yêu cầu đóng góp nó như là câu trả lời của riêng tôi. Tôi đã sử dụng đề nghị của anh ấy và hoàn thành việc thực hiện một ConfigurationManagertrình bao bọc. Về nguyên tắc, tôi chỉ cần điền ...vào câu trả lời của tvanfosson.

Không. Các phương thức mở rộng yêu cầu một thể hiện của một đối tượng. Tuy nhiên, bạn có thể viết một trình bao bọc tĩnh xung quanh giao diện Trình quản lý cấu hình. Nếu bạn triển khai trình bao bọc, bạn không cần một phương thức mở rộng vì bạn chỉ có thể thêm phương thức trực tiếp.

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

Bạn có thể sử dụng cast trên null để làm cho nó hoạt động.

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

Phần mở rộng:

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

Kiểu của bạn:

public class YourType { }

-4

Bạn CÓ THỂ làm điều này nếu bạn sẵn sàng "frig" nó một chút bằng cách tạo một biến của lớp tĩnh và gán nó thành null. Tuy nhiên, phương thức này sẽ không có sẵn cho các cuộc gọi tĩnh trên lớp, vì vậy không chắc nó sẽ sử dụng bao nhiêu:

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

đây chính xác là những gì tôi đã làm Lớp học của tôi được gọi là MyTrace :)
Gishu

Lời khuyên hữu ích. một chút mùi mã, nhưng tôi đoán chúng ta có thể ẩn đối tượng null trong một lớp cơ sở hoặc một cái gì đó. Cảm ơn.
Tom Deloford

1
Tôi không thể biên dịch mã này. Lỗi 'System.Console': các loại tĩnh không thể được sử dụng làm tham số
kuncevic.dev

Có, điều này không thể được thực hiện. Chết tiệt, tôi nghĩ rằng bạn đã vào một cái gì đó ở đó! Các kiểu tĩnh không thể được truyền dưới dạng tham số vào các phương thức có nghĩa là tôi cho rằng. Chúng ta hãy hy vọng rằng MS nhìn thấy gỗ từ những cái cây trên cái này và thay đổi nó.
Tom Deloford

3
Tôi nên đã cố gắng biên dịch mã của riêng tôi! Như Tom nói, điều này sẽ không hoạt động với các lớp tĩnh.
Tenaka
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.