Форум программистов, компьютерный форум, киберфорум
Наши страницы

C# для начинающих

Войти
Регистрация
Восстановить пароль
 
 
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
#1

Override индексатора и смена типа возвращаемого значения - C#

16.10.2017, 17:53. Просмотров 297. Ответов 15
Метки нет (Все метки)

Доброго времени суток, товарищи!
Столкнулся с одной неприятностью, подскажите пожалуйста как лучше выходить из таких ситуаций.
В общем, имеется пользовательская коллекция содержащая, скажем, экземпляры Potato, назовём её PotatoList, есть у неё индексатор
C#
1
2
3
4
public virtual PotatoList this[ int index ]
{
  // реализация
}
Виртуальный, потому что в наследниках мы будем его переопределять. И так, у нас есть наследник InheritedPotatoList, содержащий экземпляры InheritedPotato, переопределим у него индексатор... а вот тут-то у нас и проблема. Ну т.е. как проблема, скорее проблемка.
Т.е. в самом по себе переопределении проблем нет, проблема в том, что мы не можем переопределить возвращаемый тип.
Т.е. в индексаторе в геттере мы делаем всё правильно, находим нужный элемент и возвращаем его. Но при обращении к элементу через индексатор
C#
1
myInheritedList[ 0 ]
мы будем получать объект типа Potato, а не InheritedPotato. Его конечно же можно привести к нужному типу InheritedPotato, но это как бы не очень удобно и не очень красиво.
Обычно для решения этой проблемы используются дженерики и не нужно городить иерархию пользовательских коллекций. Но у меня COM библиотека, она не может в дженерики.
Есть один костыль, который мне жутко не нравится, поэтому я и обращаюсь к более опытному сообществу.
Костыль: вместо переопределения (override) использовать перекрытие, оно лояльно относится к смене возвращаемого типа, но перекрытия штука не однозначная не красивая и чреватая ошибками.
Как могут прокомментировать данную ситуацию более опытные товарищи?

Добавлено через 17 минут
Что-то не вижу возможности отредактировать пост. Там у меня небольшая опечатка в обращении через индексатор, правильно будет
C#
1
inheritedPotatoList[ 0 ]
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
16.10.2017, 17:53
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Override индексатора и смена типа возвращаемого значения (C#):

Несовместимость по доступности: доступность типа возвращаемого значения - C#
Добрый вечер. У меня возникла проблема, не могу понять ее причину. class MyPoint { public Point Cords { get; set; } ...

Несовместимость по доступности типа возвращаемого значения и метода - C#
Добрий день всем. У меня высвечивает ошибку: Несовместимость по доступности: доступность типа возвращаемого значения...

Ошибка: Доступность типа возвращаемого значения ниже доступности оператора - C#
Здравствуйте, у меня есть интерфейс IPoint, от него идут 2 класса: Point2D и Point3D, причем Point2D не предок Point3D. Далее я описал...

Работа индексатора и значимая переменная типа uint - C#
В общем снова какие-то грабли :-| Вот код. class PwrOfTwo { public uint this { get ...

Реализация IEnumerable<T>: GetEnumerator не имеет соответствующего возвращаемого типа - C#
Здравствуйте. Помогите пожалуйста разобраться с реализацией IEnumerable&lt;T&gt;. public class Group : IEnumerable&lt;Student&gt; { //... ...

Недопустимый тип возвращаемого значения - C#
Ошибка 1 &quot;string WindowsFormsApplication1.Enter.GET_3()&quot; : недопустимый тип возвращаемого значения string GET_3()//список лайкнувших ...

15
Тапок ярости
3 / 3 / 2
Регистрация: 11.02.2017
Сообщений: 40
Завершенные тесты: 1
16.10.2017, 18:13 #2
а чем апкаст неугодил ?
0
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
16.10.2017, 19:22  [ТС] #3
Цитата Сообщение от Тапок ярости Посмотреть сообщение
а чем апкаст неугодил ?
Ну наверное тем, что это не очень удобно. Ну вот допустим наш InheritedPotato имеет свойство Title, а Potato такого свойства не имеет. В итоге что мы имеем? При "правильном" индексаторе можно написать так:
C#
1
string someString = inheritedPotatoList[ 0 ].Title;
Загляденье! А что в нашем случае?
C#
1
string someString = ( ( InheritedPotato )inheritedPotatoList[ 0 ] ).Title;
или
C#
1
string someString = ( inheritedPotatoList[ 0 ] as InheritedPotato ).Title;
Мммм... как-то не очень. И не очень не только потому что это более громоздко, это не очень, потому что сам синтаксис записи inheritedPotatoList[ 0 ] предполагает получения элементов именно InheritedPotato, а тут получается не так.
0
Тапок ярости
3 / 3 / 2
Регистрация: 11.02.2017
Сообщений: 40
Завершенные тесты: 1
17.10.2017, 01:03 #4
используй интерфейсы
0
Aael
375 / 278 / 125
Регистрация: 02.06.2016
Сообщений: 487
Завершенные тесты: 1
17.10.2017, 01:48 #5
Predatore,
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A
{
    public static A<T> CommonStaticMethod<T>() {
        throw new NotImplementedException();
    }
}
 
class A<T>: A
{
    public virtual T ConcreteInstanceMethod() {
        throw new NotImplementedException();
    }
}
 
class B: A<B>
{
    public override B ConcreteInstanceMethod() {
        return this;
    }
}


Добавлено через 9 минут
картошки
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Potato {}
class InheritedPotato: Potato { }
 
class PotatoList
{
}
 
class PotatoList<TItem>: PotatoList where TItem: Potato
{
    List<TItem> innerList;
 
    public virtual TItem this[int index]
    {
        get => innerList[index];
        set => innerList[index] = value;
    }
}
 
class InheritedPotatoList: PotatoList<InheritedPotato>
{
}
0
Usaga
Эксперт .NET
2227 / 1895 / 349
Регистрация: 21.01.2016
Сообщений: 7,287
Завершенные тесты: 2
17.10.2017, 06:25 #6
Aael, человек ясно дал понять, что обобщённые классы использовать не может.

Predatore, для чего это понадобилось? Как это используется? InheritedPotato привносит что-то новое в класс или просто переопределяет существующие члены? Нужно больше информации. Может быть решение есть, но оно не такое, как вы ожидаете.

Переопределение должно сохранять сигнатуру метода, что логично.
0
woldemas
243 / 144 / 57
Регистрация: 06.09.2013
Сообщений: 483
17.10.2017, 10:41 #7
Predatore, я тоже не люблю использовать приведение между типами одной иерархии, однажды в одном проекте использовал следующий костыль:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IPotato {
    Potato AsPotato { get; }
    InheritedPotato AsInheritedPotato { get; }
}
 
class Potato : IPotato {
    Potato AsPotato { get { return this; } }
    InheritedPotato AsInheritedPotato { get { throw new InvalidCastException(); } }
}
    
class InheritedPotato : IPotato {
    Potato AsPotato { get { throw new InvalidCastException(); } }
    Potato AsInheritedPotato { get { return this; } }   
    void InheritedPotatoMember() { };
}
Далее объявляем коллекцию:
C#
1
2
3
4
5
6
7
8
9
class PotatoCollection {
    IPotato[] _items;
        // Всякие методы
    public IPotato this [int i] 
    { 
        get { return _items [i]; } 
        set { _items [i] = value; } 
    }
}
Теперь приведение типа выглядит так:
C#
1
collection[i].AsInheritedPotato.InheritedPotatoMember();
Если тип другой, то выбросит исключение разумеется.
То есть базовый интерфейс служит в основном для кастования, ну и может содержать общие объявления какие-нибудь.
В том проекте где я это реализовал, я точно знал настоящий тип экземпляра, переменной заданной интерфейсом и кастовал без страха, при выбросе исключения просто сообщал об ошибке входных данных.
Возможно это вкусовщина, но моя запись мне нравилась больше, чем as-is. К тому же если конкретная реализация экземпляра объекта точно знает свой тип - то пусть и сама себя к нему приводит.

Добавлено через 44 минуты
Да, вспомнил, лучше не интерфейс а абстрактный класс и по умолчанию реализовать все методы приведения как бросающие исключения, а в производных переопределить только по одному метод.
0
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
17.10.2017, 12:38  [ТС] #8
Цитата Сообщение от Usaga Посмотреть сообщение
Predatore, для чего это понадобилось? Как это используется? InheritedPotato привносит что-то новое в класс или просто переопределяет существующие члены? Нужно больше информации. Может быть решение есть, но оно не такое, как вы ожидаете.
Ситуация примерно такая: есть базовый класс, пусть тот же Potato, в нём собрана в основном функциональность. Абстрактным он быть не может в силу некоторых обстоятельств, но по сути это абстракция над множеством объектов с общими методами. У него есть множество наследников, конкретных классов, которые привносят очень много нового и даже ничего не переопределяют.
Над всем этим есть базовая пользовательская коллекция PotatoList и множество её соответствующих наследников.
Всё ещё несколько усугубляется тем, что некоторые из классов являются композицией из других классов, что может приводить к таким не красивым конструкциям (реальная строка кода):
C#
1
ReceivingEventExist = groupedDocument.Any( d => ( ( Event )( ( Document )d ).EventList[ 0 ] ).Title == TextConstEvent.Receiving );
Приведение as возможно смотрелось бы тут элегантнее, но у меня есть причины использовать именно такое приведение типа.

Цитата Сообщение от woldemas Посмотреть сообщение
Predatore, я тоже не люблю использовать приведение между типами одной иерархии, однажды в одном проекте использовал следующий костыль:
Интересное решение... не совсем то что хотелось, но интересное. В моём случае проблема в том, что наследников реально много. И заталкивать все эти касты в базовую реализацию... можно конечно, но как то не очень.
0
Usaga
Эксперт .NET
2227 / 1895 / 349
Регистрация: 21.01.2016
Сообщений: 7,287
Завершенные тесты: 2
17.10.2017, 13:43 #9
Predatore, хм. У вас есть масса наследников одного класса и есть масса специализированных коллекций под каждого наследника?

Добавлено через 1 минуту
Яснее не стало. Похоже на какой-то клубок.
0
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
17.10.2017, 15:51  [ТС] #10
Цитата Сообщение от Usaga Посмотреть сообщение
Predatore, хм. У вас есть масса наследников одного класса и есть масса специализированных коллекций под каждого наследника?
Добавлено через 1 минуту
Яснее не стало. Похоже на какой-то клубок.
Всё верно. Т.к. дженерики забанены, приходится делать специализированную коллекцию под каждого наследника. Стандартные списки использовать нельзя, т.к. дженерики. Массивы использовать нельзя, т.к. клиентская сторона может работать только с элементарными массивами. В итоге да, получается такой клубочек... Не от хорошей жизни я поднял этот вопрос.
0
Usaga
Эксперт .NET
2227 / 1895 / 349
Регистрация: 21.01.2016
Сообщений: 7,287
Завершенные тесты: 2
17.10.2017, 16:13 #11
Predatore, вы всё это добро выставляете через COM-интерфейс некоему потребителю на С++? Мне не совсем ясна природа такого ограничения. Особенно про массивы. Может во "внутренней кухне" всё можно нормально хранить.
0
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
17.10.2017, 23:38  [ТС] #12
Цитата Сообщение от Usaga Посмотреть сообщение
Predatore, вы всё это добро выставляете через COM-интерфейс некоему потребителю на С++? Мне не совсем ясна природа такого ограничения. Особенно про массивы. Может во "внутренней кухне" всё можно нормально хранить.
Всё это добро выставляется через COM-интерфейс некоему потребителю на C/AL, довольно специфичном языке, который знает, например, что такое массив int, но не знает что массив может быть и из каких-нибудь объектов.
Не понял про "внутреннюю кухню". Мне нужно отдать клиенту коллекцию объектов, массив я отдать не могу, стандартный список я отдать не могу, поэтому я отдаю пользовательскую коллекцию, которая по сути обёртка над стандартным списком.
Хм... сейчас пришёл в голову ещё один костыль, который наверное можно считать приемлемым в моём случае. Мне всё ещё интересно, существует ли нормальное, не костыльное решение данной проблемы, но суть только что придуманного костыля такова: с итерированием коллекции на стороне клиента у меня проблем нет, не foreach конечно, но некий похожий аналог я там изобрёл для моих коллекций. Индексатор использовался некоторое время, пока я итерировал коллекции обычным оператором for, но не так давно, я полностью перешёл на foreach-подобную конструкцию, которая не использует индексатор. Индексатор же по большому счёту используется только для обращений к первому и последнему элементам коллекций (иногда нужны только они). Отсюда можно выбрать два варианта:
1. Убрать индексатор у базовой коллекции и добавить туда парочку методов GetFirst и GetLast. В наследниках же просто реализовать индексаторы, которые уже не имеют никаких ограничений на возвращаемый тип.
2. Оставить всё как есть и добавить в наследники методы GetFirst и GetLast (которых не будет в базовой коллекции) и которые будут возвращать первый и последний элемент нужного типа.
Какой из этих двух вариантов выбрать, это уж мне самому нужно хорошенько подумать, пока мне первый нравится больше.
0
Usaga
Эксперт .NET
2227 / 1895 / 349
Регистрация: 21.01.2016
Сообщений: 7,287
Завершенные тесты: 2
18.10.2017, 10:29 #13
Predatore, на самом деле, вы можете сделать базовый класс обобщённым, перенести максимум логики туда, а над ним выстроить конкретные и очень тонкие обёртки. Индексатор или итератор тут мало на что влияют, так как у них всё равно в сигнатуре отображается конкретный тип (Potato). И, похоже, вам придётся передавать клиенту эти классы как есть, по их собственному интерфейсу, а не по базовому (ибо дженерики в бане, а сигнатуры наследников всё равно будут отличаться).

Не сказать, что решение элегантное, но хоть костылей не будет и дублирование кода самое минимальное.

На вскидку, другого в голову не приходит.

Добавлено через 2 минуты
Если же не получится передавать классы-наследники от обобщённого класса, то можно наследование заменить композицией, спрятав обобщённую натуру класса в его детали реализации вообще никак не видимые снаружи.
0
Predatore
24 / 19 / 5
Регистрация: 25.10.2009
Сообщений: 227
18.10.2017, 14:38  [ТС] #14
Цитата Сообщение от Usaga Посмотреть сообщение
Если же не получится передавать классы-наследники от обобщённого класса, то можно наследование заменить композицией, спрятав обобщённую натуру класса в его детали реализации вообще никак не видимые снаружи.
Не получится передавать класс-наследник от обобщённого, я уже наступал на эти грабли. Но мне кажется что в замене наследования композицией что-то есть. Но, если не трудно, не могли бы Вы привести кратенький пример, а то у меня картинка что-то никак не сложится.
0
Usaga
Эксперт .NET
2227 / 1895 / 349
Регистрация: 21.01.2016
Сообщений: 7,287
Завершенные тесты: 2
18.10.2017, 16:50 #15
Predatore, ну как-то так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
internal class Base<T>
{
    private List<T> collection = new List<T>();
 
    public void Add(T item)
    {
        collection.Add(item);
    }
 
    public T Get(int index)
    {
        return collection[index];
    }
}
 
public class ExportedClass
{
    private Base<Potato> internalImpl = new Base<Potato>();
 
    public void Add(potato item)
    {
        internalImpl.Add(item);
    }
 
    public Potato Get(int index)
    {
        return internalImpl.Get(index);
    }
}
1
18.10.2017, 16:50
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
18.10.2017, 16:50
Привет! Вот еще темы с ответами:

.NET 4.x Не получается перехват значения возвращаемого Main - C#
Создал файл .bat @echo off rem Командный файл для приложения SimpleCSharpApp.exe, rem перехватывающий возвращаемое им значение ...

Инкапсуляция. Поле + свойство с проверкой возвращаемого значения - C#
Задание В классе MyClass создать приватное строковое поле с именем name, а затем открытое строковое свойство с именем Name, которое...

аналог делегата Func<T> без возвращаемого значения - C#
есть ли такой делегат в стандартной библиотеке классов? или может быть есть способ указать в параметре типе void ?

Метод должен иметь тип возвращаемого значения - C#
Задание: В последовательности чисел вводимых с клавиатуры исключить все цифры 1 и 3, оставив прежним порядок оставшихся цифр. ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru