Ich finde das Fehlen indizierter Eigenschaften sehr frustrierend, wenn ich versuche, sauberen, präzisen Code zu schreiben. Eine indizierte Eigenschaft hat eine ganz andere Konnotation als die Bereitstellung einer indizierten Klassenreferenz oder die Bereitstellung einzelner Methoden. Ich finde es etwas beunruhigend, dass die Bereitstellung des Zugriffs auf ein internes Objekt, das eine indizierte Eigenschaft implementiert, sogar als akzeptabel angesehen wird, da dies häufig eine der Schlüsselkomponenten der Objektorientierung verletzt: die Kapselung.
Ich bin oft genug auf dieses Problem gestoßen, aber ich bin es heute gerade wieder aufgetreten, daher werde ich ein reales Codebeispiel bereitstellen. Die Schnittstelle und Klasse, die geschrieben wird, speichert die Anwendungskonfiguration, bei der es sich um eine Sammlung lose verwandter Informationen handelt. Ich musste benannte Skriptfragmente hinzufügen und die Verwendung des unbenannten Klassenindexers hätte einen sehr falschen Kontext impliziert, da Skriptfragmente nur ein Teil der Konfiguration sind.
Wenn indizierte Eigenschaften in C # verfügbar wären, hätte ich den folgenden Code implementieren können (Syntax ist, dass dieser [Schlüssel] in PropertyName [Schlüssel] geändert wurde).
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
}
Leider sind indizierte Eigenschaften nicht implementiert, daher habe ich eine Klasse zum Speichern implementiert und Zugriff darauf gewährt. Dies ist eine unerwünschte Implementierung, da der Zweck der Konfigurationsklasse in diesem Domänenmodell darin besteht, alle Details zu kapseln. Clients dieser Klasse greifen namentlich auf bestimmte Skriptfragmente zu und haben keinen Grund, diese zu zählen oder aufzuzählen.
Ich hätte dies umsetzen können als:
public string ScriptGet(string name)
public void ScriptSet(string name, string value)
Was ich wahrscheinlich haben sollte, aber dies ist ein nützliches Beispiel dafür, warum die Verwendung von indizierten Klassen als Ersatz für diese fehlende Funktion oft kein vernünftiger Ersatz ist.
Um eine ähnliche Funktion wie eine indizierte Eigenschaft zu implementieren, musste ich den folgenden Code schreiben, der erheblich länger, komplexer und daher schwerer zu lesen, zu verstehen und zu warten ist.
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;
}
}