Кастинг вопрос в C # - PullRequest
1 голос
/ 07 июля 2011

У меня есть интерфейс, который наследуется от IList:

public interface IBase {}
public class Derived : IBase {}
public interface IMyList : IList<IBase> {}

Я хочу привести переменную типа IMyList к типу IList<Derived> или List<Derived>, в зависимости от того, что проще или имеет смысл,Какой лучший способ сделать это?

Обратите внимание, что я использую .NET 3.5

Ответы [ 6 ]

10 голосов
/ 07 июля 2011

Прямое приведение не подходит, так как могут возникнуть многочисленные проблемы (IMyList может содержать типы, отличные от Derived и т. Д.)

Пока вы не изменяете список (в этом случае IEnumerable<Derived> будет работать), вы можете просто:

var validItems = myList.Cast<Derived>();

А затем перебрать результат.

Редактировать

Исходя из комментария ФП ниже, следует упомянуть еще две вещи:

1) Если вам нужен IList<Derived>, вы можете просто добавить к вышесказанному и позвонить myList.Cast<Derived>().ToList();

2) Если это действительно требования, то ваш код не имеет никакого смысла. Если IMyList должен содержать только Derived объектов, тогда IMyList должно быть производным от IList<Derived>. Помните, что, хотя вы знаете, что существует только один тип, реализующий интерфейс IBase, компилятор не настолько специфичен.

Использование интерфейсов везде ради использования интерфейсов никому не помогает!

4 голосов
/ 07 июля 2011

Я передаю приведенный результат в функцию, которая принимает IList<Derived>.

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

Если вы не можете этого сделать, то я бы попытался исправить функцию, которая нарушает работу, чтобы она либо принимала IList<IBase>, IEnumerable<IBase> или IEnumerable<Derived>. Предпочтительно одно из множества решений; большинство методов, которые принимают списки , на самом деле нуждаются только в последовательностях . Если бы вы могли объяснить, зачем вам нужен этот список, это поможет найти обходной путь.

Если вы можете сделать это, взяв IList<IBase> или IEnumerable<IBase>, тогда все готово; у вас уже есть что-то, что неявно преобразуется в нужный тип.

Если вы можете заставить его принять IEnumerable<Derived>, тогда вы можете сказать myListOfIBase.Cast<Derived>() (если вы действительно знаете, что все они являются производными) или myListOfIBase.OfType<Derived>() (если вы подозреваете, что некоторые из них могут не относиться к типу Derived и хотите их пропустить) и получите IEnumerable<Derived>, который эффективно использует базовый список.

Если вы не можете изменить функцию-нарушитель, тогда вы можете создать свой собственный класс, который эффективно использует базовый список:

sealed class MyProxyList : IList<Derived>
{
    IList<IBase> underlyingList;
    public MyProxyList(IList<IBase> underlyingList)
    {
        this.underlyingList = underlyingList;
    }
    ... now implement every member of IList<Derived> as 
    ... a call to underlyingList with a cast where necessary 
}

А затем передайте новый MyProxyList методу.

1 голос
/ 07 июля 2011

.Net 3.5 не имеет общей ковариации.Для демонстрации:

[TestFixture]
class Class1
{
    [Test]
    public void test()
    {
        var list = new List<SuperClass>();
        list.Add(new SuperClass());

        var castedList = ((List<BaseClass>)list);
    }
}

public class BaseClass
{
    public string a { get; set; }
}

public class SuperClass : BaseClass
{
    public string b { get; set; }
}

Не удастся скомпилировать.

В ответе Джастина Нисснера была опубликована работа вокруг

var validItems = myList.Cast<Derived>();  

.Это будет работать, но приведет к перечислению всего списка (хотя это перечисление отложено), а также вернет перечислимый.Чтобы преобразовать список и получить IList, вы можете использовать следующий

[Test]
public void CanConvertListToBaseClass()
{
    var list = new List<SuperClass>();
    list.Add(new SuperClass());

    var castedList = list.Cast<BaseClass>().ToList();
    Assert.That(castedList, Is.InstanceOf<IList<BaseClass>>());
}

Однако это довольно грубый подход.Это приведет к созданию нового отдельного IList и приведет к перечислению всего списка.

0 голосов
/ 07 июля 2011

Если вы можете быть абсолютно уверены, что в будущем все экземпляры будут иметь тип Derived, тогда вы можете использовать Cast<>() с проблемами производительности, отмеченными ранее. Если есть вероятность, что что-то, кроме Derived, когда-либо будет в списке, тогда вы захотите использовать OfType<Derived>(). Предметы, не являющиеся Derived, будут исключены вместо броска.

0 голосов
/ 07 июля 2011

Поскольку IList<T> разрешает операции чтения и записи типа T, интерфейс не является ни коинвариантным, ни контравариантным.Таким образом, то, что вы хотите, не может быть сделано.Представьте себе следующий код:

var myList = new MyListImlementation();
myList.Add(new BaseImplementation());
var castList = (IList<Derived>)myList; // this is what you want

// this would break, because myList contains elements of type BaseImplementation.
Derived d = castList[0];               
0 голосов
/ 07 июля 2011

Вы не можете сделать это в .Net 3.5, но вы можете в .Net 4.0.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...