クリーンで簡潔なコードを書き込もうとすると、インデックス付きのプロパティがないことが非常にイライラします。インデックス付きプロパティは、インデックス付きのクラス参照を提供したり、個々のメソッドを提供したりするのとは非常に異なる意味合いを持っています。インデックス付きプロパティを実装する内部オブジェクトへのアクセスを提供することは、オブジェクト指向の重要なコンポーネントの1つであるカプセル化を壊すことが多いため、許容できるとさえ見なされるのは少し気がかりです。
私はこの問題に何度も遭遇しますが、今日また問題に遭遇したので、実際のコード例を提供します。記述されているインターフェースとクラスは、ゆるやかに関連する情報のコレクションであるアプリケーション構成を格納します。名前付きスクリプトフラグメントを追加する必要がありました。スクリプトフラグメントは構成の一部にすぎないため、名前なしクラスインデクサーを使用すると、非常に間違ったコンテキストが暗示されていました。
インデックス付きのプロパティがC#で利用できる場合は、次のコードを実装できます(構文はthis [key]がPropertyName [key]に変更されています)。
public interface IConfig
{
string Scripts[string name] { get; set; }
}
internal class Config : IConfig, IXmlConfig
{
#region Application Configuraiton Settings
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
private void ClearConfig()
{
_scripts.Clear();
}
#region IXmlConfig
void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
{
Debug.Assert(configVersion == 2);
Debug.Assert(appElement != null);
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)
{
Debug.Assert(appElement != null);
ClearConfig();
if (configVersion == 2)
{
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 string ScriptGet(string name)
public void ScriptSet(string name, string value)
これはおそらく必要ですが、これは、この欠落している機能の代わりにインデックス付きクラスを使用することが合理的な代替ではないことが多い理由を示す便利な例です。
インデックス付きプロパティと同様の機能を実装するには、以下のコードを作成する必要がありました。以下のコードはかなり長く、複雑であるため、読み取り、理解、および保守が困難です。
public interface IConfig
{
ScriptsCollection Scripts { get; }
}
internal class Config : IConfig, IXmlConfig
{
public Config()
{
_scripts = new ScriptsCollection();
_scripts.ScriptChanged += ScriptChanged;
}
#region Application Configuraiton Settings
public ScriptsCollection Scripts
{ get { return _scripts; } }
private readonly ScriptsCollection _scripts;
private void ScriptChanged(object sender, ScriptChangedEventArgs e)
{
OnAppConfigChanged();
}
#endregion
private void ClearConfig()
{
_scripts.Clear();
}
#region IXmlConfig
void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
{
Debug.Assert(configVersion == 2);
Debug.Assert(appElement != null);
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)
{
Debug.Assert(appElement != null);
ClearConfig();
if (configVersion == 2)
{
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;
}
}