Eu sei, eu sei ... A resposta de Eric Lippert para esse tipo de pergunta é geralmente algo como " porque não valeu o custo de projetar, implementar, testar e documentar ".
Mesmo assim, gostaria de uma explicação melhor ... Eu estava lendo esta postagem do blog sobre os novos recursos do C # 4 e, na seção sobre interoperabilidade COM, a seguinte parte me chamou a atenção:
A propósito, este código usa mais um novo recurso: propriedades indexadas (observe mais de perto os colchetes após Range). Mas esse recurso está disponível apenas para interoperabilidade COM; você não pode criar suas próprias propriedades indexadas em C # 4.0 .
OK mas porquê ? Eu já sabia e me arrependia de não ter sido possível criar propriedades indexadas em C #, mas essa frase me fez pensar novamente a respeito. Posso ver vários bons motivos para implementá-lo:
- o CLR suporta (por exemplo,
PropertyInfo.GetValue
tem umindex
parâmetro), então é uma pena que não possamos tirar proveito disso em C # - é compatível com interoperabilidade COM, conforme mostrado no artigo (usando envio dinâmico)
- é implementado em VB.NET
- já é possível criar indexadores, ou seja, aplicar um índice ao próprio objeto, então provavelmente não seria grande coisa estender a ideia para propriedades, mantendo a mesma sintaxe e apenas substituindo
this
por um nome de propriedade
Isso permitiria escrever esse tipo de coisas:
public class Foo
{
private string[] _values = new string[3];
public string Values[int index]
{
get { return _values[index]; }
set { _values[index] = value; }
}
}
Atualmente, a única solução alternativa que conheço é criar uma classe interna ( ValuesCollection
por exemplo) que implemente um indexador e alterar a Values
propriedade para que ela retorne uma instância dessa classe interna.
Isso é muito fácil de fazer, mas irritante ... Então, talvez o compilador possa fazer isso por nós! Uma opção seria gerar uma classe interna que implemente o indexador e expô-la por meio de uma interface genérica pública:
// 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;
}
}
}
Mas é claro que, nesse caso, a propriedade retornaria realmente um IIndexer<int, string>
e não seria realmente uma propriedade indexada ... Seria melhor gerar uma propriedade indexada CLR real.
O que você acha ? Você gostaria de ver esse recurso em C #? Se não, por quê?
fonte
Respostas:
Veja como projetamos o C # 4.
Primeiro, fizemos uma lista de todos os recursos possíveis que poderíamos pensar em adicionar à linguagem.
Em seguida, agrupamos os recursos em "isso é ruim, nunca devemos fazer isso", "isso é incrível, temos que fazer" e "isso é bom, mas não vamos fazer desta vez".
Em seguida, examinamos quanto orçamento tínhamos para projetar, implementar, testar, documentar, enviar e manter os recursos "obrigatórios" e descobrimos que estávamos 100% acima do orçamento.
Então, movemos um monte de coisas do balde "tenho que ter" para o balde "bom ter".
As propriedades indexadas nunca estiveram nem perto do topo da lista "preciso ter". Eles estão muito abaixo na lista de "boas" e flertando com a lista de "más idéias".
Cada minuto que gastamos projetando, implementando, testando, documentando ou mantendo o bom recurso X é um minuto que não podemos gastar com os recursos incríveis A, B, C, D, E, F e G. Temos que priorizar implacavelmente para que apenas faça os melhores recursos possíveis. Propriedades indexadas seriam boas, mas bom não está nem perto de bom o suficiente para realmente ser implementado.
fonte
O indexador AC # é uma propriedade indexada. É nomeado
Item
por padrão (e você pode se referir a ele como, por exemplo, VB), e você pode alterá-lo com IndexerNameAttribute se quiser.Não sei por que, especificamente, ele foi projetado dessa forma, mas parece ser uma limitação intencional. No entanto, é consistente com as diretrizes de design da estrutura, que recomendam a abordagem de uma propriedade não indexada que retorna um objeto indexável para coleções de membros. Ou seja, "ser indexável" é uma característica de um tipo; se for indexável de mais de uma maneira, ele realmente deve ser dividido em vários tipos.
fonte
Como você já pode fazer isso, e é forçado a pensar em aspectos OO, adicionar propriedades indexadas apenas adicionaria mais ruído à linguagem. E apenas outra maneira de fazer outra coisa.
class Foo { public Values Values { ... } } class Values { public string this[int index] { ... } } foo.Values[0]
Eu pessoalmente prefiro ver apenas uma única maneira de fazer algo, em vez de dez maneiras. Mas é claro que esta é uma opinião subjetiva.
fonte
Eu costumava favorecer a ideia de propriedades indexadas, mas depois percebi que isso acrescentaria uma ambigüidade terrível e, na verdade, desincentivaria a funcionalidade. Propriedades indexadas significam que você não tem uma instância de coleção filho. Isso é bom e ruim. É menos problemático para implementar e você não precisa de uma referência para a classe proprietária envolvente. Mas também significa que você não pode passar essa coleção filho para nada; você provavelmente terá que enumerar todas as vezes. Nem você pode fazer um foreach sobre ele. Pior de tudo, você não pode dizer olhando para uma propriedade indexada se é isso ou uma propriedade de coleção.
A ideia é racional, mas só leva à inflexibilidade e constrangimento abrupto.
fonte
Bar
]. A expressãoThing.Bar(5)
usaria propriedade indexadaBar
emThing
, enquanto(Thing.Bar)(5)
usaria propriedade não indexadaBar
e, em seguida, usaria o indexador padrão do objeto resultante. Para minha bind, permitindoThing.Bar[5]
a ser uma propriedadeThing
, em vez de uma propriedadeThing.Bar
, é bom desde que, entre outras coisas, é possível que em algum momento no tempo, o significadoThing.Bar[4]
pode ser clara, mas o efeito adequado de ...var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]
pode não estar claro. Considere também a noção de queThing
pode conter os dadosBar
em um campo que pode ser um objeto imutável compartilhado ou um objeto mutável não compartilhado; uma tentativa de gravaçãoBar
quando o campo de apoio é imutável deve fazer uma cópia mutável, mas uma tentativa de leitura não deve. SeBar
for uma propriedade indexada nomeada, o getter indexado pode deixar a coleção de apoio sozinha (seja mutável ou não) enquanto o setter pode fazer uma nova cópia mutável, se necessário.Acho a falta de propriedades indexadas muito frustrante ao tentar escrever um código limpo e conciso. Uma propriedade indexada tem uma conotação muito diferente de fornecer uma referência de classe que é indexada ou fornecer métodos individuais. Acho um pouco perturbador que fornecer acesso a um objeto interno que implementa uma propriedade indexada seja considerado aceitável, pois isso geralmente quebra um dos principais componentes da orientação do objeto: o encapsulamento.
Eu me deparo com esse problema com frequência, mas acabei de encontrá-lo novamente hoje, então vou fornecer um exemplo de código do mundo real. A interface e a classe que estão sendo gravadas armazenam a configuração do aplicativo, que é uma coleção de informações vagamente relacionadas. Eu precisava adicionar fragmentos de script nomeados e usar o indexador de classe sem nome implicaria em um contexto muito errado, pois os fragmentos de script são apenas parte da configuração.
Se as propriedades indexadas estivessem disponíveis em C #, eu poderia ter implementado o código abaixo (a sintaxe é esta [chave] alterada para PropertyName [chave]).
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 }
Infelizmente, as propriedades indexadas não são implementadas, então implementei uma classe para armazená-las e forneci acesso a ela. Esta é uma implementação indesejável porque o propósito da classe de configuração neste modelo de domínio é encapsular todos os detalhes. Os clientes desta classe acessarão fragmentos de script específicos por nome e não têm razão para contá-los ou enumerá-los.
Eu poderia ter implementado isso como:
public string ScriptGet(string name) public void ScriptSet(string name, string value)
O que eu provavelmente deveria ter feito, mas esta é uma ilustração útil de por que usar classes indexadas como um substituto para esse recurso ausente geralmente não é um substituto razoável.
Para implementar um recurso semelhante como uma propriedade indexada, tive que escrever o código abaixo, que você notará que é consideravelmente mais longo, mais complexo e, portanto, mais difícil de ler, entender e manter.
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; } }
fonte
Outra solução alternativa está listada em Criação fácil de propriedades que oferecem suporte à indexação em C # , que requer menos trabalho.
EDITAR : Devo também acrescentar que em resposta à pergunta original, que se pudermos realizar a sintaxe desejada, com suporte de biblioteca, então acho que deve haver um caso muito forte para adicioná-lo diretamente à linguagem, a fim de minimizar o inchaço da linguagem.
fonte
this[]
), eles só precisam permitir um identificador em vez dethis
. Mas duvido que algum dia seja incluído na linguagem, pelos motivos explicados por Eric em sua respostaBem, eu diria que eles não o adicionaram porque não valeu o custo de projetar, implementar, testar e documentar.
Brincadeiras à parte, provavelmente é porque as soluções alternativas são simples e o recurso nunca reduz o tempo versus benefícios. Eu não ficaria surpreso em ver isso parecer uma mudança no futuro.
Você também se esqueceu de mencionar que uma solução mais fácil é criar um método regular:
public void SetFoo(int index, Foo toSet) {...} public Foo GetFoo(int index) {...}
fonte
Existe uma solução geral simples usando lambdas para representar a funcionalidade de indexação
Para indexação somente leitura
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); } } }
Para indexação mutável
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); } } }
e uma fábrica
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); } }
no meu próprio código eu uso como
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; } } }
e com uma instância de MoineauFlankContours posso fazer
fonte
Acabei de descobrir também que você pode usar interfaces explicitamente implementadas para fazer isso, conforme mostrado aqui: Propriedade indexada nomeada em C #? (veja a segunda maneira mostrada nessa resposta)
fonte