Statyczne indeksatory?

119

Dlaczego statyczne indeksatory są niedozwolone w C #? Nie widzę powodu, dla którego nie miałyby być dozwolone, a ponadto mogłyby być bardzo przydatne.

Na przykład:

public static class ConfigurationManager 
{
        public object this[string name]
        {
            get => ConfigurationManager.getProperty(name);
            set => ConfigurationManager.editProperty(name, value);
        }

        /// <summary>
        /// This will write the value to the property. Will overwrite if the property is already there
        /// </summary>
        /// <param name="name">Name of the property</param>
        /// <param name="value">Value to be wrote (calls ToString)</param>
        public static void editProperty(string name, object value) 
        {
            var ds = new DataSet();
            var configFile = new FileStream("./config.xml", FileMode.OpenOrCreate);
            ds.ReadXml(configFile);

            if (ds.Tables["config"] == null)
                ds.Tables.Add("config");

            var config = ds.Tables["config"];

            if (config.Rows[0] == null) 
                config.Rows.Add(config.NewRow());

            if (config.Columns[name] == null) 
                config.Columns.Add(name);

            config.Rows[0][name] = value.ToString();

            ds.WriteXml(configFile);
            configFile.Close();
        }

        public static void addProperty(string name, object value) =>
            ConfigurationManager.editProperty(name, value);

        public static object getProperty(string name) 
        {
            var ds = new DataSet();
            var configFile = new FileStream("./config.xml", FileMode.OpenOrCreate);
            ds.ReadXml(configFile);
            configFile.Close();

            if (ds.Tables["config"] == null) return null;

            var config = ds.Tables["config"];

            if (config.Rows[0] == null) return null;
            if (config.Columns[name] == null) return null;

            return config.Rows[0][name];
        }
    }

Powyższy kod znacznie skorzystałby na statycznym indeksatorze. Jednak nie zostanie skompilowany, ponieważ indeksatory statyczne są niedozwolone. Dlaczego tak jest?

Malfist
źródło
Dalej chcę bezpośredniej implementacji IEnumerable w klasie statycznej, więc mogę to zrobić foreach (var enum in Enum):)
nawfal

Odpowiedzi:

72

Notacja indeksatora wymaga odwołania do this. Ponieważ metody statyczne nie mają odniesienia do żadnego konkretnego wystąpienia klasy, nie można ich używać this, a tym samym nie można używać notacji indeksującej w metodach statycznych.

Rozwiązaniem tego problemu jest użycie wzorca singleton w następujący sposób:

public class Utilities
{
    private static ConfigurationManager _configurationManager = new ConfigurationManager();
    public static ConfigurationManager ConfigurationManager => _configurationManager;
}

public class ConfigurationManager
{
    public object this[string value]
    {
        get => new object();
        set => // set something
    }
}

Teraz możesz wywoływać Utilities.ConfigurationManager["someKey"]używając notacji indeksującej.

Julia
źródło
110
Ale dlaczego indeksator musi używać „this”? Nie musi mieć dostępu do danych instancji
Malfist,
80
+1 dla komentarza Malfista. To, że używa „this” dla indeksatora instancji, nie oznacza, że ​​nie mogli wymyślić innej składni.
Jon Skeet
40
Zgoda. Błagasz o to pytanie. Zasadniczo powiedziałeś, że jest to niedozwolone, ponieważ nie jest dozwolone. -1, ponieważ pytanie brzmiało „dlaczego jest to niedozwolone?”
xr280xr
15
@ xr280xr +1 Za poprawne użycie "błagam o pytanie" :) Plus mam ten sam zarzut.
RedFilter
14
-1, ponieważ ta odpowiedź zakłada, że ​​bieżąca notacja jest jedyną możliwą notacją, jeśli zaimplementowano indeksator statyczny. Użycie thisw indeksatorze niekoniecznie jest wymagane, prawdopodobnie zostało wybrane ponad innymi słowami kluczowymi, ponieważ miało największy sens. Dla statycznego realizacji następujące składnia może być bardzo opłacalne: public object static[string value]. Nie ma potrzeby używania słowa kluczowego thisw kontekście statycznym.
einsteinsci
91

Uważam, że uznano to za niezbyt przydatne. Myślę, że to też wstyd - przykładem, którego zwykle używam, jest kodowanie, gdzie Encoding.GetEncoding("foo")mogłoby być Encoding["Foo"]. Nie sądzę, by pojawiało się to zbyt często, ale poza wszystkim innym wydaje się to trochę niekonsekwentne, że nie jest dostępny.

Musiałbym to sprawdzić, ale podejrzewam, że jest już dostępny w języku IL (język pośredni).

Jon Skeet
źródło
6
Język pośredni - rodzaj asemblera dla .NET.
Jon Skeet
15
To co mnie tu sprowadziło to to, że mam niestandardową klasę, która udostępnia słownik wspólnych wartości używanych w mojej aplikacji za pośrednictwem właściwości statycznej. Miałem nadzieję, że użyję statycznego indeksatora, aby skrócić dostęp z GlobalState.State [KeyName] do samego GlobalState [KeyName]. Byłoby miło.
xr280xr
1
FWIW, zmiana instancena staticw IL dla właściwości i metody pobierającej dla właściwości domyślnej powoduje narzekanie ilasm syntax error at token 'static'; Nie jestem świetny w wtrącaniu się w sprawy IL, ale to brzmi jak przynajmniej początkowe „nie”.
Amazingant
8

Aby obejść ten problem, możesz zdefiniować indeksator instancji na obiekcie pojedynczym / statycznym (powiedzmy, że ConfigurationManager jest singletonem, a nie klasą statyczną):

class ConfigurationManager
{
  //private constructor
  ConfigurationManager() {}
  //singleton instance
  public static ConfigurationManager singleton;
  //indexer
  object this[string name] { ... etc ... }
}
ChrisW
źródło
1

Potrzebowałem również (no cóż, raczej przyjemnego do posiadania) statycznego indeksatora do przechowywania atrybutów, więc znalazłem nieco niezręczne obejście:

W klasie, w której chcesz mieć statyczny indeksator (tutaj: Element), utwórz podklasę o tej samej nazwie + „Dict”. Nadaj mu tylko do odczytu static jako wystąpienie wspomnianej podklasy, a następnie dodaj żądany indeksator.

Na koniec dodaj klasę jako import statyczny (stąd podklasa, aby ujawnić tylko pole statyczne).

import static Element.ElementDict;

public class Element {
    // .... 
    private static readonly Dictionary<string, object> elemDict = new Dictionary<string, object>();
    public class ElementDict {
        public readonly static ElementDict element = new ElementDict();
        public object this[string key] {
            get => elemDict.TryGetValue(key, out object o) ? o : null;
            set => elemDict[key] = value;
        }
    }
}

a następnie możesz użyć go wielkimi literami jako Typ lub bez jako słownika:

var cnt = element["counter"] as int;
element["counter"] = cnt;

Ale niestety, gdybyśmy faktycznie użyli obiektu jako "wartości" -Type, to poniżej byłby jeszcze krótszy (przynajmniej jako deklaracja), a także zapewniłby natychmiastowe rzutowanie:

public static T load<T>(string key) => elemDict.TryGetValue(key, out object o) ? (T) o : default(T);
public static void store<T>(string key, T value) => elemDict[key] = value;

var cnt = Element.load<int>("counter");
Element.store("counter", cnt);
DW.com
źródło
0

Dzięki nowszym konstrukcjom w języku C # 6 można uprościć wzorzec singleton za pomocą treści wyrażenia właściwości. Na przykład użyłem następującego skrótu, który działa dobrze z lense kodu:

public static class Config
{
   public static NameValueCollection Get => ConfigurationManager.AppSettings;
}

Dodatkową zaletą jest możliwość znajdowania i zastępowania w celu uaktualnienia starszego kodu i ujednolicenia dostępu do ustawień aplikacji.

vGHazard
źródło
-2

Słowo kluczowe this odnosi się do bieżącej instancji klasy. Statyczne funkcje składowe nie mają wskaźnika this. Słowa kluczowego this można używać do uzyskiwania dostępu do elementów członkowskich z poziomu konstruktorów, metod instancji i akcesorów instancji (pobieranych z msdn ). Ponieważ odwołuje się do instancji klasy, koliduje z naturą static, ponieważ static nie jest skojarzone z instancją klasy.

Jedynym obejściem byłoby następujące, które umożliwia użycie indeksatora w odniesieniu do prywatnego słownika, więc wystarczy utworzyć nowe wystąpienie i uzyskać dostęp do części statycznej.

    public class ConfigurationManager 
{
    public ConfigurationManager()
    {
        // TODO: Complete member initialization
    }
    public object this[string keyName]
    {
        get
        {
                return ConfigurationManagerItems[keyName];
        }
        set
        {
                ConfigurationManagerItems[keyName] = value;
        }
    }
    private static Dictionary<string, object> ConfigurationManagerItems = new Dictionary<string, object>();        
}

Pozwala to pominąć cały proces uzyskiwania dostępu do członka klasy i po prostu utworzyć jego instancję i ją zindeksować.

    new ConfigurationManager()["ItemName"]
lamorach
źródło
4
jest to interesujące obejście, ale 1) wprowadza efekty uboczne (tworzenie pustego obiektu instancji), które mogą prowadzić do nacisku pamięci i fragmentacji w niektórych środowiskach, 2) dodatkowe znaki marnowane przez new ()mogłyby zostać użyte do nazwy kwalifikatora singletona zamiast tego, jak.Current
Lawrence Ward,
1
Podobnie jak odpowiedź Juliet , nie odpowiada to na pytanie, dlaczego indeksatory statyczne nie są obsługiwane. Po pierwsze, pytanie nie ogranicza terminu „indeksator statyczny” do „czegoś, co używa thissłowa kluczowego”, a po drugie, thisw składni public string this[int index]ściśle mówiąc, nie jest to nawet użycie thiswskaźnika (jak może się to zdarzyć w treści metod instancji) , ale po prostu kolejne użycie tokena this . Składnia public static string this[int index]może wyglądać nieco sprzecznie z intuicją, ale nadal byłaby jednoznaczna.
LUB Mapper
2
@ORMapper Równie dobrze mogłoby być public static string class[int index].
Jim Balter
Myślę, że jestem zdezorientowany. Pomyślałem, że to słowo kluczowe odnosi się do bieżącej instancji klasy. Statyczne funkcje składowe nie mają tego wskaźnika. ' wyjaśniał, że skoro nie ma obiektu, do którego ten wskaźnik mógłby się odwoływać, nie można użyć odniesienia do niego. Stwierdziłem również, że msdn jest tym, który używa tej definicji. Public static string vs public string nigdy nie będą się nakładać, jeśli chodzi o moją wiedzę, ponieważ jeden uzyskuje dostęp do obiektu typu ogólnego, a drugi uzyskuje dostęp do obiektu instancji.
lamorach
-2

Powodem jest to, że dość trudno jest zrozumieć, co dokładnie indeksujesz za pomocą statycznego indeksatora.

Mówisz, że kod skorzystałby na statycznym indeksatorze, ale czy naprawdę? Wszystko, co by zrobił, to zmienić to:

ConfigurationManager.editProperty(name, value);
...
value = ConfigurationManager.getProperty(name)

Zaangażowany w to:

ConfigurationManager[name] = value
...
value = ConfigurationManager[name]

co w żaden sposób nie czyni kodu lepszym; nie jest mniejszy o wiele linii kodu, nie jest łatwiejszy do pisania dzięki autouzupełnianiu i jest mniej czytelny, ponieważ ukrywa fakt, że otrzymujesz i ustawiasz coś, co nazywasz `` właściwością '' i faktycznie zmusza czytelnika do przeczytaj dokumentację na temat tego, co dokładnie indeksator zwraca lub ustawia, ponieważ nie jest w żaden sposób oczywiste, że jest to właściwość, dla której indeksujesz, podczas gdy oba:

ConfigurationManager.editProperty(name, value);
...
value = ConfigurationManager.getProperty(name)

Możesz przeczytać to na głos i od razu zrozumieć, co robi kod.

Pamiętaj, że chcemy napisać kod, który jest łatwy (= szybki) do zrozumienia, a nie kod, który można szybko napisać. Nie myl szybkości, z jaką można układać kod, z szybkością, z jaką wykonujesz projekty.

ciasto
źródło
8
Nie zgadzać się. To tylko kwestia koncepcyjna. Moim zdaniem kod wygląda lepiej, chociaż to tylko moja opinia.
ouflak