Wiem, wiem ... Odpowiedź Erica Lipperta na tego typu pytanie brzmi zwykle mniej więcej w stylu: „ ponieważ nie było warto zaprojektować, wdrożyć, przetestować i udokumentować ”.
Mimo to chciałbym uzyskać lepsze wyjaśnienie ... Czytałem ten wpis na blogu o nowych funkcjach C # 4 , aw sekcji o COM Interop moją uwagę przykuła następująca część:
Nawiasem mówiąc, ten kod wykorzystuje jeszcze jedną nową funkcję: indeksowane właściwości (przyjrzyj się bliżej tym nawiasom kwadratowym po polu Zakres). Ale ta funkcja jest dostępna tylko dla współdziałania COM; nie można tworzyć własnych indeksowanych właściwości w C # 4.0 .
Dobrze ale dlaczego ? Wiedziałem już i żałowałem, że nie można utworzyć indeksowanych właściwości w C #, ale to zdanie skłoniło mnie do ponownego przemyślenia. Widzę kilka dobrych powodów, aby to wdrożyć:
- CLR to obsługuje (na przykład
PropertyInfo.GetValue
maindex
parametr), więc szkoda, że nie możemy tego wykorzystać w C # - jest obsługiwany w przypadku współpracy z COM, jak pokazano w artykule (przy użyciu dynamicznej wysyłki)
- jest zaimplementowany w VB.NET
- jest już możliwe tworzenie indeksatorów, tj. zastosowanie indeksu do samego obiektu, więc prawdopodobnie nie byłoby wielkim problemem rozszerzenie pomysłu na właściwości, zachowując tę samą składnię i zastępując po prostu
this
nazwą właściwości
Pozwoliłoby napisać takie rzeczy:
public class Foo
{
private string[] _values = new string[3];
public string Values[int index]
{
get { return _values[index]; }
set { _values[index] = value; }
}
}
Obecnie jedynym znanym mi obejściem jest utworzenie klasy wewnętrznej ( ValuesCollection
na przykład), która implementuje indeksator i zmiana Values
właściwości, tak aby zwracała instancję tej klasy wewnętrznej.
Jest to bardzo łatwe, ale irytujące ... Więc może kompilator mógłby to zrobić za nas! Opcją byłoby wygenerowanie klasy wewnętrznej, która implementuje indeksator i ujawnienie jej za pośrednictwem publicznego interfejsu ogólnego:
// interface defined in the namespace System
public interface IIndexer<TIndex, TValue>
{
TValue this[TIndex index] { get; set; }
}
public class Foo
{
private string[] _values = new string[3];
private class <>c__DisplayClass1 : IIndexer<int, string>
{
private Foo _foo;
public <>c__DisplayClass1(Foo foo)
{
_foo = foo;
}
public string this[int index]
{
get { return _foo._values[index]; }
set { _foo._values[index] = value; }
}
}
private IIndexer<int, string> <>f__valuesIndexer;
public IIndexer<int, string> Values
{
get
{
if (<>f__valuesIndexer == null)
<>f__valuesIndexer = new <>c__DisplayClass1(this);
return <>f__valuesIndexer;
}
}
}
Ale oczywiście w takim przypadku właściwość faktycznie zwróciłaby a IIndexer<int, string>
, a tak naprawdę nie byłaby właściwością indeksowaną ... Lepiej byłoby wygenerować rzeczywistą właściwość indeksowaną w środowisku CLR.
Co myślisz ? Czy chcesz zobaczyć tę funkcję w języku C #? Jeśli nie, dlaczego?
źródło
Odpowiedzi:
Oto, jak zaprojektowaliśmy C # 4.
Najpierw sporządziliśmy listę wszystkich możliwych funkcji, które moglibyśmy dodać do języka.
Następnie podzieliliśmy funkcje na „to jest złe, nigdy nie możemy tego robić”, „to jest niesamowite, musimy to zrobić” i „to jest dobre, ale nie róbmy tego tym razem”.
Następnie przyjrzeliśmy się, jaki budżet mieliśmy na zaprojektowanie, wdrożenie, przetestowanie, udokumentowanie, dostarczenie i utrzymanie funkcji „trzeba mieć” i odkryliśmy, że przekroczyliśmy budżet o 100%.
Więc przenieśliśmy kilka rzeczy z wiadra „muszę mieć” do wiadra „miło mieć”.
Indeksowane właściwości były nigdy nigdzie w pobliżu górnej części listy „muszę mieć”. Są bardzo nisko na liście „fajnych” i flirtują z listą „złych pomysłów”.
Każda minuta, którą spędzamy na projektowaniu, wdrażaniu, testowaniu, dokumentowaniu lub utrzymywaniu fajnej funkcji X to minuta, której nie możemy poświęcić na niesamowite funkcje A, B, C, D, E, F i G. Musimy bezwzględnie ustalić priorytety, abyśmy tylko zrobić najlepsze możliwe funkcje. Indeksowane właściwości byłyby fajne, ale ładne nie są nawet wystarczająco dobre, aby faktycznie zostały zaimplementowane.
źródło
Indeksator AC # to właściwość indeksowana. Jest on
Item
domyślnie nazwany (i możesz się do niego odwoływać np. Z VB) i możesz go zmienić za pomocą IndexerNameAttribute, jeśli chcesz.Nie jestem pewien, dlaczego, konkretnie, został zaprojektowany w ten sposób, ale wydaje się, że jest to celowe ograniczenie. Jest to jednak zgodne z wytycznymi dotyczącymi projektowania struktury, które zalecają podejście nieindeksowanej właściwości zwracającej indeksowany obiekt dla kolekcji elementów członkowskich. To znaczy „możliwość indeksowania” jest cechą pewnego typu; jeśli jest indeksowalny na więcej niż jeden sposób, to naprawdę powinien zostać podzielony na kilka typów.
źródło
Ponieważ w pewnym sensie możesz to już zrobić i zmusza cię to do myślenia w aspektach OO, dodanie indeksowanych właściwości po prostu doda więcej szumu do języka. I po prostu inny sposób na zrobienie innej rzeczy.
class Foo { public Values Values { ... } } class Values { public string this[int index] { ... } } foo.Values[0]
Osobiście wolałbym widzieć tylko jeden sposób zrobienia czegoś, a nie 10 sposobów. Ale oczywiście jest to subiektywna opinia.
źródło
Kiedyś faworyzowałem ideę indeksowanych właściwości, ale potem zdałem sobie sprawę, że dodałoby to okropną niejednoznaczność i faktycznie zniechęciłoby funkcjonalność. Indeksowane właściwości oznaczałyby, że nie masz instancji kolekcji podrzędnej. To jest dobre i złe. Wdrożenie jest mniej kłopotliwe i nie potrzebujesz odniesienia z powrotem do otaczającej klasy właściciela. Ale oznacza to również, że nie możesz przekazać tej podrzędnej kolekcji do niczego; prawdopodobnie będziesz musiał wyliczyć za każdym razem. Nie możesz też tego zrobić. Co najgorsze, patrząc na indeksowaną właściwość nie można stwierdzić, czy jest to ta właściwość, czy też właściwość kolekcji.
Pomysł jest racjonalny, ale prowadzi tylko do braku elastyczności i nagłej niezręczności.
źródło
Bar
.]. WyrażenieThing.Bar(5)
użyje właściwości indeksowanejBar
onThing
, podczas gdy(Thing.Bar)(5)
użyje właściwości niezindeksowanej,Bar
a następnie użyje domyślnego indeksatora wynikowego obiektu. W moim przekonaniu pozwolenieThing.Bar[5]
na bycie własnościąThing
, a nie własnościąThing.Bar
, jest dobre, bo między innymi możliwe jest, że w pewnym momencie znaczenieThing.Bar[4]
może być jasne, ale właściwy efekt ...var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]
może być niejasne. Rozważ również koncepcję, któraThing
może przechowywać daneBar
w polu, które może być wspólnym niezmiennym obiektem lub niewspółdzielonym, zmiennym obiektem; próba zapisu,Bar
gdy pole zapasowe jest niezmienne, powinna spowodować utworzenie mutowalnej kopii, ale próba odczytu z niego nie powinna. JeśliBar
jest to nazwana właściwość indeksowana, indeksowany pobierający może pozostawić kolekcję zapasową w spokoju (bez względu na to, czy jest modyfikowalna, czy nie), podczas gdy metoda ustawiająca może w razie potrzeby utworzyć nową mutowalną kopię.Brak indeksowanych właściwości jest bardzo frustrujący, gdy próbuję napisać czysty, zwięzły kod. Właściwość indeksowana ma zupełnie inne konotacje niż dostarczanie odwołania do klasy, które jest indeksowane lub udostępnia indywidualne metody. Uważam za nieco niepokojące, że zapewnienie dostępu do wewnętrznego obiektu, który implementuje indeksowaną właściwość, jest nawet uważane za dopuszczalne, ponieważ często łamie jeden z kluczowych elementów orientacji obiektowej: hermetyzację.
Wystarczająco często napotykam ten problem, ale właśnie dziś napotkałem go ponownie, więc przedstawię przykład kodu z prawdziwego świata. Interfejs i tworzona klasa przechowuje konfigurację aplikacji, która jest zbiorem luźno powiązanych informacji. Musiałem dodać nazwane fragmenty skryptów, a użycie nienazwanego indeksatora klas spowodowałoby bardzo zły kontekst, ponieważ fragmenty skryptu są tylko częścią konfiguracji.
Gdyby indeksowane właściwości były dostępne w C #, mógłbym zaimplementować poniższy kod (składnia to [klucz] zmieniony na PropertyName [klucz]).
public interface IConfig { // Other configuration properties removed for examp[le /// <summary> /// Script fragments /// </summary> string Scripts[string name] { get; set; } } /// <summary> /// Class to handle loading and saving the application's configuration. /// </summary> internal class Config : IConfig, IXmlConfig { #region Application Configuraiton Settings // Other configuration properties removed for examp[le /// <summary> /// Script fragments /// </summary> public string Scripts[string name] { get { if (!string.IsNullOrWhiteSpace(name)) { string script; if (_scripts.TryGetValue(name.Trim().ToLower(), out script)) return script; } return string.Empty; } set { if (!string.IsNullOrWhiteSpace(name)) { _scripts[name.Trim().ToLower()] = value; OnAppConfigChanged(); } } } private readonly Dictionary<string, string> _scripts = new Dictionary<string, string>(); #endregion /// <summary> /// Clears configuration settings, but does not clear internal configuration meta-data. /// </summary> private void ClearConfig() { // Other properties removed for example _scripts.Clear(); } #region IXmlConfig void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement) { Debug.Assert(configVersion == 2); Debug.Assert(appElement != null); // Saving of other properties removed for example if (_scripts.Count > 0) { var scripts = new XElement("Scripts"); foreach (var kvp in _scripts) { var scriptElement = new XElement(kvp.Key, kvp.Value); scripts.Add(scriptElement); } appElement.Add(scripts); } } void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement) { // Implementation simplified for example Debug.Assert(appElement != null); ClearConfig(); if (configVersion == 2) { // Loading of other configuration properites removed for example var scripts = appElement.Element("Scripts"); if (scripts != null) foreach (var script in scripts.Elements()) _scripts[script.Name.ToString()] = script.Value; } else throw new ApplicaitonException("Unknown configuration file version " + configVersion); } #endregion }
Niestety indeksowane właściwości nie są zaimplementowane, więc zaimplementowałem klasę do ich przechowywania i zapewniłem do tego dostęp. Jest to niepożądana implementacja, ponieważ celem klasy konfiguracji w tym modelu domeny jest hermetyzacja wszystkich szczegółów. Klienci tej klasy będą uzyskiwać dostęp do określonych fragmentów skryptów po nazwie i nie będą mieli powodu, aby je liczyć lub wyliczać.
Mogłem to zaimplementować jako:
public string ScriptGet(string name) public void ScriptSet(string name, string value)
Co prawdopodobnie powinienem mieć, ale jest to przydatna ilustracja, dlaczego używanie klas indeksowanych jako zamienników tej brakującej funkcji często nie jest rozsądnym substytutem.
Aby zaimplementować podobne możliwości jako właściwość indeksowaną, musiałem napisać poniższy kod, który, jak zauważysz, jest znacznie dłuższy, bardziej złożony, a przez to trudniejszy do odczytania, zrozumienia i utrzymania.
public interface IConfig { // Other configuration properties removed for examp[le /// <summary> /// Script fragments /// </summary> ScriptsCollection Scripts { get; } } /// <summary> /// Class to handle loading and saving the application's configuration. /// </summary> internal class Config : IConfig, IXmlConfig { public Config() { _scripts = new ScriptsCollection(); _scripts.ScriptChanged += ScriptChanged; } #region Application Configuraiton Settings // Other configuration properties removed for examp[le /// <summary> /// Script fragments /// </summary> public ScriptsCollection Scripts { get { return _scripts; } } private readonly ScriptsCollection _scripts; private void ScriptChanged(object sender, ScriptChangedEventArgs e) { OnAppConfigChanged(); } #endregion /// <summary> /// Clears configuration settings, but does not clear internal configuration meta-data. /// </summary> private void ClearConfig() { // Other properties removed for example _scripts.Clear(); } #region IXmlConfig void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement) { Debug.Assert(configVersion == 2); Debug.Assert(appElement != null); // Saving of other properties removed for example if (_scripts.Count > 0) { var scripts = new XElement("Scripts"); foreach (var kvp in _scripts) { var scriptElement = new XElement(kvp.Key, kvp.Value); scripts.Add(scriptElement); } appElement.Add(scripts); } } void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement) { // Implementation simplified for example Debug.Assert(appElement != null); ClearConfig(); if (configVersion == 2) { // Loading of other configuration properites removed for example var scripts = appElement.Element("Scripts"); if (scripts != null) foreach (var script in scripts.Elements()) _scripts[script.Name.ToString()] = script.Value; } else throw new ApplicaitonException("Unknown configuration file version " + configVersion); } #endregion } public class ScriptsCollection : IEnumerable<KeyValuePair<string, string>> { private readonly Dictionary<string, string> Scripts = new Dictionary<string, string>(); public string this[string name] { get { if (!string.IsNullOrWhiteSpace(name)) { string script; if (Scripts.TryGetValue(name.Trim().ToLower(), out script)) return script; } return string.Empty; } set { if (!string.IsNullOrWhiteSpace(name)) Scripts[name.Trim().ToLower()] = value; } } public void Clear() { Scripts.Clear(); } public int Count { get { return Scripts.Count; } } public event EventHandler<ScriptChangedEventArgs> ScriptChanged; protected void OnScriptChanged(string name) { if (ScriptChanged != null) { var script = this[name]; ScriptChanged.Invoke(this, new ScriptChangedEventArgs(name, script)); } } #region IEnumerable public IEnumerator<KeyValuePair<string, string>> GetEnumerator() { return Scripts.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } public class ScriptChangedEventArgs : EventArgs { public string Name { get; set; } public string Script { get; set; } public ScriptChangedEventArgs(string name, string script) { Name = name; Script = script; } }
źródło
Inne obejście jest wymienione w Łatwe tworzenie właściwości, które obsługują indeksowanie w C # , co wymaga mniej pracy.
EDYCJA : Powinienem również dodać, że w odpowiedzi na pierwotne pytanie, że jeśli potrafimy osiągnąć żądaną składnię, z obsługą biblioteki, to myślę, że potrzeba bardzo mocnych argumentów, aby dodać ją bezpośrednio do języka, aby zminimalizować wzdęcia językowe.
źródło
this[]
), po prostu muszą zezwolić na identyfikator zamiastthis
. Ale wątpię, czy kiedykolwiek zostanie uwzględniony w języku, z powodów wyjaśnionych przez Erica w swojej odpowiedziCóż, powiedziałbym, że go nie dodali, ponieważ nie było to warte kosztów projektowania, wdrażania, testowania i dokumentowania.
Żarty na bok, prawdopodobnie dlatego, że obejścia są proste, a funkcja nigdy nie skraca czasu w porównaniu z korzyściami. Nie zdziwiłbym się jednak, gdyby okazało się, że to zmiana w przyszłości.
Zapomniałeś również wspomnieć, że łatwiejszym obejściem jest po prostu wykonanie zwykłej metody:
public void SetFoo(int index, Foo toSet) {...} public Foo GetFoo(int index) {...}
źródło
Istnieje proste, ogólne rozwiązanie wykorzystujące lambdy do proxy funkcji indeksowania
Do indeksowania tylko do odczytu
public class RoIndexer<TIndex, TValue> { private readonly Func<TIndex, TValue> _Fn; public RoIndexer(Func<TIndex, TValue> fn) { _Fn = fn; } public TValue this[TIndex i] { get { return _Fn(i); } } }
Do indeksowania zmiennego
public class RwIndexer<TIndex, TValue> { private readonly Func<TIndex, TValue> _Getter; private readonly Action<TIndex, TValue> _Setter; public RwIndexer(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter) { _Getter = getter; _Setter = setter; } public TValue this[TIndex i] { get { return _Getter(i); } set { _Setter(i, value); } } }
i fabrykę
public static class Indexer { public static RwIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter) { return new RwIndexer<TIndex, TValue>(getter, setter); } public static RoIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter) { return new RoIndexer<TIndex, TValue>(getter); } }
we własnym kodzie używam go jak
public class MoineauFlankContours { public MoineauFlankContour Rotor { get; private set; } public MoineauFlankContour Stator { get; private set; } public MoineauFlankContours() { _RoIndexer = Indexer.Create(( MoineauPartEnum p ) => p == MoineauPartEnum.Rotor ? Rotor : Stator); } private RoIndexer<MoineauPartEnum, MoineauFlankContour> _RoIndexer; public RoIndexer<MoineauPartEnum, MoineauFlankContour> FlankFor { get { return _RoIndexer; } } }
i za pomocą instancji MoineauFlankContours mogę to zrobić
źródło
Właśnie się przekonałem, że możesz użyć jawnie zaimplementowanych interfejsów, aby to osiągnąć, jak pokazano tutaj: Nazwana indeksowana właściwość w C #? (zobacz drugi sposób pokazany w tej odpowiedzi)
źródło