Я знаю, я знаю ... Эрик Липперт обычно отвечает на этот вопрос примерно так: « Потому что это не стоило затрат на его разработку, внедрение, тестирование и документирование ».
Но все же мне хотелось бы получить более подробное объяснение ... Я читал это сообщение в блоге о новых функциях C # 4 , и в разделе о COM-взаимодействии мое внимание привлекла следующая часть:
Кстати, в этом коде используется еще одна новая функция: индексированные свойства (обратите внимание на квадратные скобки после Range). Но эта функция доступна только для COM-взаимодействия; вы не можете создавать свои собственные индексированные свойства в C # 4.0 .
Хорошо, но почему ? Я уже знал и сожалел о невозможности создания индексированных свойств в C #, но это предложение заставило меня снова задуматься об этом. Я вижу несколько веских причин для его реализации:
- CLR поддерживает его (например,
PropertyInfo.GetValue
имеетindex
параметр), поэтому жаль, что мы не можем воспользоваться этим в C # - он поддерживается для COM-взаимодействия, как показано в статье (с использованием динамической отправки)
- реализовано на VB.NET
- уже можно создавать индексаторы, то есть применять индекс к самому объекту, поэтому, вероятно, не составит большого труда распространить идею на свойства, сохранив тот же синтаксис и просто заменив
this
имя свойства
Это позволило бы писать такие вещи:
public class Foo
{
private string[] _values = new string[3];
public string Values[int index]
{
get { return _values[index]; }
set { _values[index] = value; }
}
}
В настоящее время единственный известный мне обходной путь - это создать внутренний класс ( 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;
}
}
}
Но, конечно, в этом случае свойство действительно вернет a IIndexer<int, string>
и не будет на самом деле индексированным свойством ... Было бы лучше сгенерировать реальное индексированное свойство CLR.
Что вы думаете ? Хотели бы вы увидеть эту функцию на C #? Если нет, то почему?
источник
Ответы:
Вот как мы разработали C # 4.
Сначала мы составили список всех возможных функций, которые мы могли бы добавить в язык.
Затем мы добавили функции «это плохо, мы никогда не должны этого делать», «это круто, мы должны это сделать» и «это хорошо, но давайте не будем делать это в этот раз».
Затем мы посмотрели, сколько бюджета у нас было на разработку, внедрение, тестирование, документирование, поставку и поддержку необходимых функций, и обнаружили, что мы на 100% превышаем бюджет.
Таким образом, мы переместили кучу вещей из ведра «нужно иметь» в ведро «хорошо иметь».
Индексированные свойства никогда не были где - нибудь недалеко от вершины «должен иметь» список. Они очень низкие в списке «хороших» и заигрывают со списком «плохих идей».
Каждую минуту, которую мы тратим на разработку, внедрение, тестирование, документирование или поддержку хорошей функции X - это минута, которую мы не можем потратить на отличные функции A, B, C, D, E, F и G. Мы должны безжалостно расставлять приоритеты, чтобы мы только сделать все возможное. Индексированные свойства были бы хороши, но даже близко к тому, чтобы их можно было реализовать, даже близко нет.
источник
Индексатор AC # - это индексируемое свойство. Он назван
Item
по умолчанию (и вы можете ссылаться на него, например, в VB), и вы можете изменить его с помощью IndexerNameAttribute, если хотите.Я не уверен, почему именно так оно было разработано, но, похоже, это намеренное ограничение. Однако это согласуется с Рекомендациями по проектированию Framework, которые рекомендуют подход, когда неиндексированное свойство возвращает индексируемый объект для коллекций членов. Т.е. «быть индексируемым» - это характеристика типа; если он индексируется более чем одним способом, его действительно следует разделить на несколько типов.
источник
Поскольку вы уже можете это сделать, и это заставило вас думать в аспектах объектно-ориентированного программирования, добавление индексированных свойств просто добавит больше шума в язык. И просто еще один способ сделать что-то еще.
class Foo { public Values Values { ... } } class Values { public string this[int index] { ... } } foo.Values[0]
Я лично предпочел бы видеть только один способ сделать что-то, а не 10 способов. Но конечно это субъективное мнение.
источник
Раньше я отдавал предпочтение идее индексированных свойств, но потом понял, что это добавит ужасной двусмысленности и фактически лишит функциональности. Индексированные свойства означают, что у вас нет экземпляра дочерней коллекции. Это и хорошо, и плохо. Это проще реализовать, и вам не нужна обратная ссылка на включающий класс владельца. Но это также означает, что вы не можете передать эту дочернюю коллекцию чему-либо; вам, вероятно, придется перечислять каждый раз. Вы также не можете выполнить на нем foreach. Хуже всего то, что, глядя на проиндексированное свойство, нельзя сказать, является ли это свойством коллекции.
Идея рациональная, но только приводит к негибкости и резкой неловкости.
источник
Bar
]. ВыражениеThing.Bar(5)
будет использовать индексированное свойствоBar
onThing
, тогда как(Thing.Bar)(5)
будет использовать неиндексированное свойство,Bar
а затем использовать индексатор по умолчанию для полученного объекта. На мой взгляд, разрешениеThing.Bar[5]
быть свойствомThing
, а не свойствомThing.Bar
, - это хорошо, поскольку, среди прочего, возможно, что в какой-то момент времени значение словаThing.Bar[4]
может быть ясным, но надлежащий эффект ...var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]
может быть непонятным. Рассмотрим также понятие, котороеThing
может содержать данныеBar
в поле, которое может быть либо совместно используемым неизменяемым объектом, либо неразделенным изменяемым объектом; попытка записи в то время,Bar
когда резервное поле является неизменяемым, должна создавать изменяемую копию, а попытка чтения из нее - нет. ЕслиBar
это именованное индексированное свойство, индексированный метод получения может оставить резервную коллекцию в покое (изменяемой или нет), в то время как метод установки может при необходимости создать новую изменяемую копию.Я считаю, что отсутствие индексированных свойств очень расстраивает, когда я пытаюсь написать чистый и лаконичный код. Индексированное свойство имеет совершенно иной смысл, чем предоставление индексированной ссылки на класс или предоставление отдельных методов. Меня немного беспокоит то, что предоставление доступа к внутреннему объекту, реализующему индексированное свойство, даже считается приемлемым, поскольку это часто нарушает один из ключевых компонентов объектной ориентации: инкапсуляцию.
Я сталкиваюсь с этой проблемой достаточно часто, но сегодня я столкнулся с ней снова, поэтому я приведу пример кода из реального мира. Написанный интерфейс и класс хранят конфигурацию приложения, которая представляет собой набор слабо связанной информации. Мне нужно было добавить именованные фрагменты сценария, и использование безымянного индексатора классов означало бы очень неправильный контекст, поскольку фрагменты сценария являются только частью конфигурации.
Если бы индексированные свойства были доступны в C #, я мог бы реализовать приведенный ниже код (синтаксис: this [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 }
К сожалению, индексированные свойства не реализованы, поэтому я реализовал класс для их хранения и предоставил к нему доступ. Это нежелательная реализация, потому что целью класса конфигурации в этой модели предметной области является инкапсуляция всех деталей. Клиенты этого класса будут обращаться к определенным фрагментам скрипта по имени, и у них нет причин для их подсчета или перечисления.
Я мог бы реализовать это как:
public string ScriptGet(string name) public void ScriptSet(string name, string value)
Вероятно, мне следовало бы это сделать, но это полезная иллюстрация того, почему использование индексированных классов в качестве замены этой отсутствующей функции часто не является разумной заменой.
Чтобы реализовать аналогичную возможность в качестве индексированного свойства, мне пришлось написать приведенный ниже код, который, как вы заметите, значительно длиннее, сложнее и, следовательно, сложнее читать, понимать и поддерживать.
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; } }
источник
Другой обходной путь указан в разделе Простое создание свойств, поддерживающих индексацию в C # , которое требует меньше усилий.
РЕДАКТИРОВАТЬ : Я также должен добавить, что в ответ на исходный вопрос, что, если мы сможем выполнить желаемый синтаксис с поддержкой библиотеки, тогда я думаю, что должны быть очень веские аргументы, чтобы добавить его непосредственно в язык, чтобы свести к минимуму языковой раздувание.
источник
this[]
), им просто нужно разрешить идентификатор вместоthis
. Но я сомневаюсь, что он когда-либо будет включен в язык, по причинам, объясненным Эриком в его ответеЧто ж, я бы сказал, что они не добавили его, потому что это не стоило затрат на его разработку, внедрение, тестирование и документирование.
Шутя в сторону, вероятно, это потому, что обходные пути просты, и эта функция никогда не сокращает время по сравнению с выгодой. Я не удивлюсь, если в будущем это станет изменением.
Вы также забыли упомянуть, что более простой способ решения проблемы - это просто использовать обычный метод:
public void SetFoo(int index, Foo toSet) {...} public Foo GetFoo(int index) {...}
источник
Существует простое общее решение с использованием лямбда-выражений для прокси-функции индексирования.
Для индексации только для чтения
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); } } }
Для изменяемой индексации
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); } } }
и фабрика
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); } }
в моем собственном коде я использую его как
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; } } }
и с экземпляром MoineauFlankContours я могу сделать
источник
Я тоже только что узнал, что для этого можно использовать явно реализованные интерфейсы, как показано здесь: Именованное индексированное свойство в C #? (см. второй способ, показанный в этом ответе)
источник