Tho*_*que 80 c# language-features indexed-properties
我知道,我知道...... Eric Lippert对这类问题的回答通常是" 因为它不值得设计,实施,测试和记录它的成本 ".
但是,我还是想要一个更好的解释......我正在阅读关于新C#4功能的博客文章,在关于COM Interop的部分中,以下部分引起了我的注意:
顺便说一句,这段代码使用了另外一个新功能:索引属性(仔细查看Range之后的那些方括号.)但是此功能仅适用于COM互操作; 您无法在C#4.0中创建自己的索引属性.
好的,但为什么呢?我已经知道并且后悔在C#中创建索引属性是不可能的,但这句话让我再次思考它.我可以看到几个很好的理由来实现它:
PropertyInfo.GetValue有一个index参数),所以遗憾的是我们无法在C#中利用它this属性名称可能没什么大不了的.它可以写出那种东西:
public class Foo
{
private string[] _values = new string[3];
public string Values[int index]
{
get { return _values[index]; }
set { _values[index] = value; }
}
}
Run Code Online (Sandbox Code Playgroud)
目前我知道的唯一解决方法是创建一个ValuesCollection实现索引器的内部类(例如),并更改Values属性以便它返回该内部类的实例.
这很容易做到,但很烦人......所以也许编译器可以为我们做!一个选项是生成一个实现索引器的内部类,并通过公共通用接口公开它:
// 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,当然,在这种情况下,属性实际上会返回一个IIndexer<int, string>,而不是真正的索引属性......生成一个真正的CLR索引属性会更好.
你怎么看 ?你想在C#中看到这个功能吗?如果没有,为什么?
Eri*_*ert 113
以下是我们设计C#4的方法.
首先,我们列出了我们可以考虑添加到语言中的每个可能的功能.
然后我们将这些功能分解为"这很糟糕,我们绝不能这样做","这太棒了,我们必须这样做","这很好,但这次不要这样做".
然后我们看看我们有多少预算来设计,实施,测试,记录,发布和维护"必备"功能,并发现我们超出预算100%.
因此,我们将一堆东西从"得到的"桶中移到了"好有"的桶中.
索引属性从未接近 "必须拥有"列表的顶部.他们在"好"的名单上非常低,并且与"坏主意"列表调情.
我们花费在设计,实现,测试,记录或维护好的特征X上的每一分钟都是我们不能花费在令人敬畏的特征A,B,C,D,E,F和G上的一分钟.我们必须无情地优先考虑,以便我们只尽可能做到最好的功能.索引属性会很好,但是好的并不是任何地方甚至接近足够好以实际实现.
Pav*_*aev 19
AC#indexer 是一个索引属性.它Item默认命名(您可以从VB中引用它),如果需要,可以使用IndexerNameAttribute更改它.
我不确定为什么,特别是它的设计方式,但它似乎是一个故意的限制.但是,它与框架设计指南一致,它建议使用非索引属性的方法返回成员集合的可索引对象.即"可索引"是一种类型的特征; 如果它可以多种方式转换,那么它应该分成几种类型.
Ion*_*rel 14
因为你已经可以做到这一点了,并且它迫使你在OO方面进行思考,添加索引属性只会给语言增加更多噪音.而另一种方法是做另一件事.
class Foo
{
public Values Values { ... }
}
class Values
{
public string this[int index] { ... }
}
foo.Values[0]
Run Code Online (Sandbox Code Playgroud)
我个人更愿意只看到一种做事的方式,而不是10种方式.但这当然是一种主观意见.
小智 8
我过去喜欢索引属性的想法,但后来意识到它会增加可怕的模糊性,实际上会阻碍功能.索引属性意味着您没有子集合实例.这既好又坏.它实现起来不那么麻烦,您不需要引用回到封闭的所有者类.但这也意味着你无法将那个子集合传递给任何东西; 你可能每次都要列举一次.你也不能做一个foreach.最糟糕的是,您无法通过查看索引属性来判断它是否是集合属性.
这个想法是理性的,但它只会导致不灵活和突然的尴尬.
小智 5
我发现在编写干净,简洁的代码时,缺少索引属性非常令人沮丧.索引属性与提供索引或提供单个方法的类引用具有非常不同的含义.我觉得有点令人不安的是,提供对实现索引属性的内部对象的访问甚至被认为是可接受的,因为它经常会破坏面向对象的关键组件之一:封装.
我经常遇到这个问题,但我今天刚刚遇到它,所以我将提供一个真实的代码示例.正在编写的接口和类存储应用程序配置,该应用程序配置是松散相关信息的集合.我需要添加命名脚本片段,并且使用未命名的类索引器会隐含一个非常错误的上下文,因为脚本片段只是配置的一部分.
如果索引属性在C#中可用,我可以实现以下代码(语法是将此[key]更改为PropertyName [key]).
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
}
Run Code Online (Sandbox Code Playgroud)
不幸的是索引属性没有实现,所以我实现了一个类来存储它们并提供对它的访问.这是一种不合需要的实现,因为此域模型中配置类的目的是封装所有细节.此类的客户端将按名称访问特定的脚本片段,并且没有理由对它们进行计数或枚举.
我可以实现这个:
public string ScriptGet(string name)
public void ScriptSet(string name, string value)
Run Code Online (Sandbox Code Playgroud)
我可能应该这样做,但这是一个有用的例子,说明为什么使用索引类作为这个缺失特征的替代品往往不是一个合理的替代品.
要实现与索引属性类似的功能,我必须编写下面的代码,您会注意到这些代码更长,更复杂,因此更难以阅读,理解和维护.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
26626 次 |
| 最近记录: |