Varolan bir statik sınıfa genişletme yöntemleri ekleyebilir miyim?


534

Ben C # uzatma yöntemlerinin bir hayranıyım, ama Konsol gibi statik bir sınıfa bir uzatma yöntemi ekleyerek başarılı olmadı.

Örneğin, Konsol'a 'WriteBlueLine' adlı bir uzantı eklemek istersem, böylece gidebilirim:

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

Bu 'bu' parametre olarak Konsol ile yerel, genel statik yöntem ekleyerek denedim ... ama zar yok!

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

Bu Konsol 'WriteBlueLine' yöntemi eklemedi ... Yanlış mı yapıyorum? Yoksa imkansızı mı istiyorsun?


3
Oh iyi. talihsiz ama bence ben hallederim. Ben hala bir üretim yöntemi bakire STILL (zaten üretim kodunda). Belki bir gün, eğer şanslıysam.
Andy McCluggage

ASP.NET MVC için bir dizi HtmlHelper uzantısı yazdım. DateTime için bana verilen tarihin sonunu (23: 59.59) vermesi için bir tane yazdı. Kullanıcıdan bir bitiş tarihi belirtmesini istediğinizde, ancak gerçekten o günün sonu olmasını istediğinizde yardımcı olur.
tvanfosson

12
Özellik C # 'da mevcut olmadığından bunları şu anda eklemenin bir yolu yoktur. Kendi başına imkansız olduğu için değil , ancak C # peep'leri çok meşgul olduğu için, çoğunlukla LINQ'nun çalışması için uzatma yöntemleriyle ilgileniyorlardı ve statik uzatma yöntemlerinde uygulamak için gereken zamanı haklı çıkarmak için yeterli fayda görmediler. Eric Lippert burada açıklıyor .
Jordan Grey

1
Sadece ara Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

Yanıtlar:


285

Hayır. Uzantı yöntemleri, bir nesne için bir örnek değişkeni (değer) gerektirir. Ancak, ConfigurationManagerarabirimin etrafına statik bir sarıcı yazabilirsiniz . Sarıcıyı uygularsanız, yöntemi doğrudan ekleyebileceğiniz için bir uzantı yöntemine ihtiyacınız yoktur.

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

      .....

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

8
@Luis - bağlamda, fikir "belirli bir bölümü almak için ConfigurationManager sınıfına bir uzantı yöntemi ekleyebilir miyim?" Nesnenin bir örneğini gerektirdiğinden statik sınıfa bir uzantı yöntemi ekleyemezsiniz, ancak aynı imzayı uygulayan ve gerçek ConfigurationManager'a gerçek çağrıyı savunan bir sarıcı sınıfı (veya cephe) yazabilirsiniz. Bir uzantı olması gerekmediği için sarmalayıcı sınıfına istediğiniz yöntemi ekleyebilirsiniz.
tvanfosson

Ben sadece ConfigurationSection uygulayan sınıfa statik bir yöntem eklemek daha yararlı buluyorum. Bu yüzden, MyConfigurationSection adlı bir uygulama verildiğinde, zaten yazılan bölümü döndüren MyConfigurationSection.GetSection () yöntemini veya yoksa yoksa null değerini çağırırdım. Sonuç aynıdır, ancak sınıf eklemekten kaçınır.
dokunun

1
@tap - bu sadece bir örnek ve ilk akla gelen örnek. Yine de tek sorumluluk prensibi devreye girer. "Kapsayıcı" aslında yapılandırma dosyasından kendisini yorumlamaktan sorumlu olmalı mıdır? Normalde ben sadece ConfigurationSectionHandler var ve ConfigurationManager gelen çıkış uygun sınıfa döküm ve sarıcı ile rahatsız etmeyin.
tvanfosson

5
Şirket içi kullanım için, özel Uzantılar eklemek için statik sınıfların ve yapıların 'X' varyantlarını oluşturmaya başladım: 'ConsoleX', 'Console' için yeni statik yöntemler içerir, 'MathX' 'Math', 'ColorX' için yeni statik yöntemler içerir IntelliSense'te aynı değil, hatırlanması ve keşfedilmesi kolay.
user1689175

1
@Xtro Kötü olduğunu kabul ediyorum, ancak yerinde bir test çiftini kullanamamaktan daha kötü değil ya da daha kötüsü, kodunuzu test etmekten vazgeçin çünkü statik sınıflar bunu zorlaştırıyor. MVC için statik HttpContext.Current etrafında almak için HttpContextWrapper / HttpContextBase sınıflarını tanıttı nedeni budur, benimle aynı fikirde gibi görünüyor.
tvanfosson

91

C # 'daki sınıflara statik uzantılar ekleyebilir misiniz? Hayır ama bunu yapabilirsiniz:

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

İşte böyle. Statik uzatma yöntemlerini teknik olarak yazamasanız da, bu kod uzantı yöntemlerinde bir boşluk kullanır. Bu boşluk, null istisnasını almadan null nesneler üzerinde uzantı yöntemlerini çağırabilmenizdir (@this aracılığıyla herhangi bir şeye erişmediğiniz sürece).

İşte bunu nasıl kullanacağınız:

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

Şimdi neden örnek olarak varsayılan kurucuyu çağırmayı seçtim ve VE neden tüm İfade çöplerini yapmadan ilk kod snippet'inde yeni T () döndürmüyorum? Bugün şanslı gününüz çünkü 2fer alırsınız. Herhangi bir gelişmiş .NET geliştiricisinin bildiği gibi, yeni T () yavaştır çünkü System.Activator çağrısını yapmadan önce varsayılan yapıcıyı almak için yansıma kullanan bir çağrı oluşturur. Lanet olsun Microsoft! Ancak benim kod doğrudan nesnenin varsayılan yapıcı çağırır.

Statik uzantılar bundan daha iyi olurdu, ancak umutsuz zamanlar umutsuz önlemler gerektirir.


2
Dataset için bu hile işe yarayacağını düşünüyorum, ama Konsol statik sınıf olduğu için Konsol sınıfı için çalıştığından şüpheleniyorum, statik türler argüman olarak kullanılamıyor :)
ThomasBecker

Evet, aynı şeyi söyleyecektim. Bu, statik olmayan bir sınıfta sözde statik genişletme yöntemidir. OP, statik bir sınıfta bir genişletme yöntemiydi.
Mark A. Donohoe

2
Sadece gibi bu tür yöntemler için bazı adlandırma kuralı mevcut çok daha iyi ve daha kolay için var XConsole, ConsoleHelperve böyle devam eder.
Alex Zhukovskiy

9
Bu büyüleyici bir hile ama sonuç koklamak. Sen null bir nesne oluşturmak, daha sonra üzerinde "bir null nesne üzerinde bir yöntemi çağırmak bir istisna neden" söylenen yıllar rağmen bir yöntem çağırmak gibi görünüyor. İşe yarıyor, ama ..ugh ... Daha sonra bakacak kimseye kafa karıştırıcı. Aşağı inmeyeceğim, çünkü neyin mümkün olduğuna dair bilgi havuzuna eklediniz. Ama içtenlikle umarım kimse bu tekniği kullanmaz !! Ek şikayet: Bunlardan birini bir yönteme geçirmeyin ve OO alt sınıfını almayı bekleyin: çağrılan yöntem , iletilen parametre türü değil , parametre bildirimi türü olacaktır .
ToolmakerSteve

5
Bu zor, ama hoşuma gitti. Bir alternatif (null as DataSet).Create();olabilir default(DataSet).Create();.
Bagerfahrer

54

Bu mümkün değil.

Ve evet bence MS burada bir hata yaptı.

Kararları mantıklı değil ve programcıları (yukarıda açıklandığı gibi) anlamsız bir sarmalayıcı sınıfı yazmaya zorlamaktadır.

İşte iyi bir örnek: Statik MS Unit test sınıfını genişletmeye çalışıyorum Assert: 1 tane daha Assert yöntemi istiyorum AreEqual(x1,x2).

Bunu yapmanın tek yolu, farklı sınıflara işaret etmek veya 100'lerce farklı Assert yönteminin etrafına bir sarıcı yazmaktır. Neden!?

Örneklerin uzatılmasına izin verme kararı alınmışsa, statik uzantılara izin vermemek için mantıklı bir neden göremiyorum. Örnekler genişletilebildiğinde, kitaplık bölümlemeyle ilgili argümanlar durmaz.


20
Ayrıca Assert.Throws ve Assert.DoesNotThrow eklemek için MS Unit Test sınıfı Assert genişletmek çalışıyordu ve aynı sorunla karşı karşıya.
Stefano Ricciardi

3
Evet ben de :( Assert.ThrowsCevabı yapabileceğimi düşündüm stackoverflow.com/questions/113395/…
CallMeLaNN

27

Ben OP aynı soruya bir cevap bulmaya çalışırken bu konu üzerinde tökezledi. İstediğim cevabı bulamadım ama bunu yaptım.

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

Ve şu şekilde kullanıyorum:

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

19

Belki de özel ad alanınız ve aynı sınıf adınızla statik bir sınıf ekleyebilirsiniz:

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
Ancak bu, paketinizde tutmak istediğiniz orijinal statik sınıftan her yöntemi yeniden uygulamak zorunda kalma sorununu çözmez . Yine de bir sarıcı, onu kullanan kodda daha az değişikliğe ihtiyaç
duymanın esası


11

Hayır! Uzantı yöntemi tanımları, genişlettiğiniz türün bir örneğini gerektirir. Talihsiz; Neden gerekli olduğundan emin değilim ...


4
Bunun nedeni, bir nesnenin bir örneğini genişletmek için bir uzantı yönteminin kullanılmasıdır. Eğer bunu yapmazlarsa, sadece düzenli statik yöntemler olurdu.
Derek Ekins

31
Her ikisini de yapmak güzel olurdu, değil mi?

7

Genişletme yöntemlerine gelince, genişleme yöntemlerinin kendileri statiktir; ancak örnek yöntemmiş gibi çağrılırlar. Statik sınıf somutlaştırılamadığından, hiçbir zaman bir uzantı yöntemini çağırmak için sınıfın bir örneğine sahip olmazsınız. Bu nedenle derleyici, statik sınıflar için genişletme yöntemlerinin tanımlanmasına izin vermez.

Sayın Obnoxious şöyle yazdı: "Herhangi bir gelişmiş .NET geliştiricisinin bildiği gibi, yeni T () yavaştır, çünkü çağırmadan önce varsayılan kurucuyu almak için yansıma kullanan System.Activator çağrısı üretir."

New (), tür derleme zamanında biliniyorsa, IL "newobj" komutuna derlenir. Newobj, doğrudan çağırma için bir kurucu alır. System.Activator.CreateInstance () yöntemine yapılan çağrılar, System.Activator.CreateInstance () yöntemini çağırmak için IL "call" komutunu derler. Yeni () genel türlere karşı kullanıldığında System.Activator.CreateInstance () çağrısına neden olur. Bay Obnoxious'un görevi bu noktada belirsizdi ... ve iyi, iğrenç.

Bu kod:

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

bu IL'yi üretir:

  .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

Bir türe statik yöntemler ekleyemezsiniz . Bir türün örneğine yalnızca (sözde-) örnek yöntemleri ekleyebilirsiniz.

Değiştiricinin noktası this, C # derleyicisine örneği .statik / uzantı yönteminin ilk parametresi olarak sol tarafında geçirmesini bildirmektir .

Bir türe statik yöntemler eklenmesi durumunda, ilk parametre için iletilecek örnek yoktur.


4

Uzatma yöntemlerini öğrenirken başarılı olamadığım zamanlarda bunu System.Environment ile yapmaya çalıştım. Bunun nedeni, diğerlerinin de belirttiği gibi, genişletme yöntemleri sınıfın bir örneğini gerektirir.


3

Bir uzantı yöntemi yazmak mümkün değildir, ancak istediğiniz davranışı taklit etmek mümkündür.

using FooConsole = System.Console;

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

Bu, diğer sınıflarda Console.WriteBlueLine (fooText) öğesini çağırmanıza izin verir. Diğer sınıflar, Konsol'un diğer statik işlevlerine erişmek istiyorsa, ad alanlarından açıkça başvurulması gerekir.

Hepsini tek bir yerde tutmak istiyorsanız, tüm yöntemleri yeni sınıfa ekleyebilirsiniz.

Yani şöyle bir şey olurdu

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

Bu, aradığınız davranış türünü sağlayacaktır.

* Not Konsolun içine koyduğunuz ad alanı ile eklenmesi gerekecektir.


1

evet, sınırlı bir anlamda.

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

Bu çalışıyor, ancak Konsol statik olmadığı için çalışmıyor.

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

Bu aynı ad alanında olmadığı sürece çalışır. Sorun, System.Console sahip her yöntem için bir proxy statik yöntemi yazmak zorunda olmasıdır. Bunun gibi bir şey ekleyebileceğiniz için mutlaka kötü bir şey değildir:

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

veya

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

Çalışma şekli, standart WriteLine'a bir şey bağlamanızdır. Bir satır sayısı veya kötü kelime filtresi ya da her neyse olabilir. Ad alanınızda Konsol'u her belirlediğinizde WebProject1 deyin ve System ad alanını içe aktarın, WebProject1 ad alanındaki sınıflar için varsayılan olarak WebProject1.Console System.Console üzerinden seçilecektir. Bu nedenle, bu kod tüm Console.WriteLine çağrılarını hiç System.Console.WriteLine belirtmediğiniz sürece maviye dönüştürecektir.


ne yazık ki temel sınıf mühürlendiğinde bir torun kullanma yaklaşımı işe yaramaz (.NET sınıfı kitaplığındaki birçok kişi gibi)
George Birbilis

1

Aşağıdakiler, tvanfosson'un cevabı için bir düzenleme olarak reddedildi . Kendi cevabım olarak katkıda bulunmam istendi. Onun önerisini kullandım ve bir ConfigurationManagerambalajın uygulamasını bitirdim . Prensip olarak sadece ...tvanfosson'un cevabını doldurdum .

Hayır. Uzantı yöntemleri bir nesne örneği gerektirir. Ancak, ConfigurationManager arabiriminin etrafına statik bir sarıcı yazabilirsiniz. Sarıcıyı uygularsanız, yöntemi doğrudan ekleyebileceğiniz için bir uzantı yöntemine ihtiyacınız yoktur.

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

Çalışmasını sağlamak için null üzerinde bir cast kullanabilirsiniz.

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

Uzantı:

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

Senin tipin:

public class YourType { }

-4

Bunu, statik sınıfın bir değişkenini yapıp null değerine atayarak biraz "dondurmak" istiyorsanız yapabilirsiniz. Ancak, bu yöntem sınıftaki statik çağrılar için kullanılamaz, bu nedenle ne kadar kullanacağından emin değilsiniz:

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

tam da bunu yaptım. Sınıfım MyTrace olarak adlandırılıyor :)
Gishu

Yararlı ipucu. kod kokusu biraz, ama sanırım biz null nesneyi bir temel sınıfta falan gizlemek. Teşekkürler.
Tom Deloford

1
Bu kodu derleyemiyorum. 'System.Console' hatası: statik türler parametre olarak kullanılamaz
kuncevic.dev

Evet, bu yapılamaz. Kahretsin orada bir şeylerin olduğunu düşündüm! Statik türler, sanırım mantıklı olan yöntemlere parametre olarak geçirilemez. Diyelim ki MS bu ağaçtaki ağaçların ahşabını görüp değiştirsin.
Tom Deloford

3
Kendi kodumu derlemeyi denemeliydim! Tom'un dediği gibi, bu statik sınıflarla çalışmaz.
Tenaka
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.