#c# #generics #dynamic #reflection #runtime
Вопрос:
У меня есть неродовой IList
, который я хотел бы использовать в зависимости от enum
значения во время выполнения.
Я не могу изменить тип неродового IList в реализации, потому что это код библиотеки.
Также я не хочу использовать отражение (потому что оно медленное) или динамическое ключевое слово (потому что это небезопасно и может привести к ошибкам в будущем).
В коде библиотеки есть следующий класс:
public class ListView //THIS IS LIBRARY CODE AND CAN NOT BE CHANGED { public IList itemsSource { get; set; } }
Затем в моем классе CustomListView, который наследуется от ListView, я хочу привести ItemsSource к соответствующему классу в зависимости от типа элемента.
Еще одним ограничением является то, что CustomListView не может быть универсальным (диктуется используемой нами библиотекой).
public class CustomListView : ListView { public dynamic GetItems() { switch (ItemType) { case ItemType.A: return itemsSource.Castlt;Agt;().ToList(); case ItemType.B: return itemsSource.Castlt;Bgt;().ToList(); default: throw new InvalidOperationException("Not supported"); } } }
Но я хочу, чтобы он напрямую возвращал правильный тип вместо использования dynamic.
Что-то вроде (приведенный ниже код не работает!):
public IListlt;Tgt; GetItems(ItemType itemType) //The type of T should change depending on what i return { switch (ItemType) { case ItemType.A: return itemsSource.Castlt;Agt;().ToList(); case ItemType.B: return itemsSource.Castlt;Bgt;().ToList(); default: throw new InvalidOperationException("Not supported"); } }
Как бы я это реализовал?
Правка/Добавление
Как вы, ребята, заметили, я должен прояснить некоторые вещи.
Классы A и B действительно имеют один и тот же базовый класс. Но я хочу иметь возможность не приводить его снова из базового типа (потому что я уже привел его в метод GetItems (), а также известно значение ItemType).
Я хочу иметь возможность делать следующее
IListlt;Agt; aItems = listView.GetItems()
Без кастинга.
Идея всего этого заключается в том, чтобы иметь общий пользовательский список, который может обрабатывать несколько типов элементов. Эти элементы будут добавлены в ItemSource. Тип этих элементов определяется типом элемента.
Я использую его так, например
public class UiForClassA { public void Foo() { CustomListView customListView = new CustomListView(ItemType.A); IListlt;Agt; itemsOfCustomListView = customListView.GetItems(); //No cast needed because it should somehow implicitly know that. } }
Я не хочу использовать приведения везде, где я использую представление CustomListView. Он каким-то образом должен неявно возвращать правильные элементы.
И к вашему сведению, я использую фреймворк Unity UI Toolkit, но на самом деле это не имеет отношения к вопросу.
Комментарии:
1. Если для классов » A » и » B «нет общей функциональности, вам следует вернуть» IListlt;объектgt;». Но у вас, вероятно, должна быть какая-то абстракция, такая как общий интерфейс. Затем вы сможете вернуть » IListlt;CommonInterfacegt;».
2. Вы не можете этого сделать. Именно вызывающий объект задает параметр типа. Вам пришлось бы объявить
public IListlt;Tgt; GetItemslt;Tgt;()
и назвать его сvar list = GetItemslt;Agt;();
помощью . ЕслиGetItems
возвращает различные типы списков, как вы хотите их использовать? Потребитель также должен быть универсальным.
Ответ №1:
Подумайте о том, как будет использоваться этот метод:
ItemType itemType = ...; var x = listView.GetItems(itemType);
Ценность itemType
может быть A
, или это может быть B
. Итак, каким должен быть этот тип x
? Это либо IListlt;Agt;
, либо IListlt;Bgt;
, и единственный способ выразить это-использовать тип, от которого оба IListlt;Agt;
и IListlt;Bgt;
наследуются, что возвращает нас назад IList
. Если вы не знаете тип, который вам нужен во время компиляции, то нет особого смысла пытаться изменить возвращаемый тип на что-то более конкретное, потому что вы не сможете получить к нему доступ как к более конкретному типу без размышлений.
Если вы знаете тип, который вам нужен во время компиляции, то предоставьте перегрузки метода, возвращающего нужный тип:
public IListlt;Agt; GetAItems() { ... } public IListlt;Bgt; GetBItems() { ... }
Редактировать
Если вы действительно хотите использовать универсальный метод, вы могли бы сделать что-то вроде этого:
public IListlt;Tgt; GetItemslt;Tgt;() { return itemsSource.Castlt;Tgt;().ToList(); }
Но я не вижу смысла в создании метода для этого, вместо того, чтобы помещать этот код непосредственно там, где вам нужен конкретный тип.
Правка 2
Ознакомившись с вашим вариантом использования, я бы действительно рекомендовал вам сделать CustomListView
класс универсальным. Я знаю, вы говорите, что библиотека предотвращает это, но есть способы обойти это. Вы создали ItemType
перечисление, вместо этого вы могли бы создать подкласс CustomListViewlt;Tgt;
для каждого типа элемента:
public class CustomListViewlt;Tgt; : ListView { public IListlt;Tgt; GetItems() =gt; itemsSources.Castlt;Tgt;().ToList(); } public class CustomListViewOfA : CustomListViewlt;Agt; { } public class CustomListViewOfB : CustomListViewlt;Bgt; { } public class UiForClassA { public void Foo() { var customListView = new CustomListViewOfA(); var itemsOfCustomListView = customListView.GetItems(); //No cast needed } }
Комментарии:
1. Просто я явно задал тип элемента, но все равно должен привести его вручную в определенном коде. Поэтому мне нужен был метод, который неявно возвращает правильный тип в зависимости от типа элемента. Я не хочу помещать оператор Cast в каждое использование этого представления списка, я хочу, чтобы для этого использовался один общий метод.
2. Я все еще не понимаю, зачем вам
ItemType
вообще нужна переменная. Было бы полезно, если бы вы показали нам, что вы пытаетесь сделать с предметами. Похоже, вы используете библиотеку/фреймворк пользовательского интерфейса — если вы предоставите некоторые сведения о классе, содержащем представление списка, и попытаетесь получить доступ к элементам определенного типа, люди смогут предложить лучшие предложения
Ответ №2:
Как вы предполагаете использовать такой метод? Параметр type задается вызывающим методом. Установка параметра типа с помощью метода невозможна. Пожалуйста, укажите способ использования.
Вы можете попробовать эти:
IListlt;Tgt; GetItemslt;Tgt;(ItemType itemType) { switch (itemType) { case ItemType.A: return (IListlt;Tgt;)(IList)(itemsSource.Castlt;Agt;().ToList()); case ItemType.B: return (IListlt;Tgt;)(IList)(itemsSource.Castlt;Bgt;().ToList()); default: throw new InvalidOperationException("Not supported"); } }
IList GetItems(ItemType itemType) { switch (itemType) { case ItemType.A: return itemsSource.Castlt;Agt;().ToList(); case ItemType.B: return itemsSource.Castlt;Bgt;().ToList(); default: throw new InvalidOperationException("Not supported"); } }
Или, как предложил @Qwertyluk в комментариях, что-то вроде этого:
private IListlt;objectgt; GetItems(ItemType itemType) { switch (itemType) { case ItemType.A: case ItemType.B: return itemsSource.Castlt;objectgt;().ToList(); default: throw new InvalidOperationException("Not supported"); } }