Как вы можете вернуть коллекцию <ConcreteType>как коллекцию <Interface>? - PullRequest
2 голосов
/ 26 августа 2009

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

Я сейчас использую .NET 2.0

Приведенный ниже код приводит к ошибке компилятора:

Невозможно неявно преобразовать тип 'System.Collections.ObjectModel.Collection ' для 'System.Collections.ObjectModel.Collection

Комментируемая попытка приведения выдает эту ошибку компилятора:

Невозможно преобразовать тип 'System.Collections.ObjectModel.Collection ' до
'System.Collections.ObjectModel.Collection ' через преобразование ссылок, преобразование в бокс, преобразование в распаковку, упаковка преобразование или преобразование нулевого типа.

Есть ли способ представить коллекцию конкретных типов как коллекцию интерфейсов или мне нужно создать новую коллекцию в методе получения интерфейса?

using System.Collections.ObjectModel;

public interface IBucket
{
    Collection<INail> Nails
    {
        get;
    }
}

public interface INail
{
}

internal sealed class Nail : INail
{
}

internal sealed class Bucket : IBucket
{
    private Collection<Nail> nails;

    Collection<INail> IBucket.Nails
    {
        get
        {
            //return (nails as Collection<INail>);
            return nails;
        }
    }

    public Bucket()
    {
        this.nails = new Collection<Nail>();
    }
}

Ответы [ 9 ]

6 голосов
/ 26 августа 2009

Просто определите ногти как

Collection<INail>
6 голосов
/ 26 августа 2009

C # 3.0 дженерики инвариантны. Вы не можете сделать это без создания нового объекта. В C # 4.0 введена безопасная ковариация / контравариантность, которая в любом случае ничего не изменит для коллекций чтения / записи (ваш случай).

1 голос
/ 26 августа 2009

Почему бы просто не вернуть его как интерфейс, просто включить все открытые методы в интерфейс, чтобы у вас не было этой проблемы, и, если позже вы решите вернуть другой тип класса Nail, тогда он будет работать нормально .

0 голосов
/ 09 июля 2014

Вы можете добавить некоторые дженерики. Подходит лучше, прочнее в сочетании.

public interface IBucket<T> where T : INail
{
    Collection<T> Nails
    {
        get;
    }
}

public interface INail
{
}

internal sealed class Nail : INail
{
}

internal sealed class Bucket : IBucket<Nail>
{
    private Collection<Nail> nails;

    Collection<Nail> IBucket<Nail>.Nails
    {
        get
        {
            return nails; //works
        }
    }

    public Bucket()
    {
        this.nails = new Collection<Nail>();
    }
}

Таким образом, Collection<Nail>, который вы возвращаете из класса Bucket, может содержать только Nail с. Любой другой INail не пойдет на это. Это может быть или не быть лучше в зависимости от того, что вы хотите.


Только если вы хотите, чтобы Collection<INail> (свойство интерфейса) вы возвращали из Bucket для хранения других INail с (кроме Nail с), тогда вы можете попробовать следующий подход. Но есть проблема. С одной стороны вы говорите, что хотите использовать private Collection<Nail> в Bucket классе, а не Collection<INail>, потому что вы не хотите случайно добавлять в него другие INail s из Bucket класса, но с другой стороны вы будете должны добавить другие INail с за пределами Bucket класса. Это невозможно в одном экземпляре . Компилятор останавливает случайное добавление INail к Collection<Nail>. Один из способов - вернуть экземпляр Collection<INail> из вашего класса Bucket из существующего Collection<Nail>. Это менее эффективно, но может быть семантикой, к которой вы стремитесь. Обратите внимание, что это концептуально отличается от описанного выше

internal sealed class Bucket : IBucket
{
    private Collection<Nail> nails;

    Collection<INail> IBucket<Nail>.Nails
    {
        get
        {
            List<INail> temp = new List<INail>();
            foreach (Nail nail in nails)
                temp.Add(nail);

            return new Collection<INail>(temp);  
        }
    }

    public Bucket()
    {
        this.nails = new Collection<Nail>();
    }
}
0 голосов
/ 26 августа 2009

C # не поддерживает ковариацию общих коллекций (поддерживается только для массивов). Я использую класс адаптера в таких случаях. Он просто перенаправляет все вызовы в фактическую коллекцию, преобразуя значения в требуемый тип (не требует копирования всех значений списка в новую коллекцию) Использование выглядит так:

Collection<INail> IBucket.Nails
{
    get
    {
        return new ListAdapter<Nail, INail>(nails);
    }
}

    // my implementation (it's incomplete)
    public class ListAdapter<T_Src, T_Dst> : IList<T_Dst>
{
    public ListAdapter(IList<T_Src> val)
    {
        _vals = val;
    }

    IList<T_Src> _vals;

    protected static T_Src ConvertToSrc(T_Dst val)
    {
        return (T_Src)((object)val);
    }

    protected static T_Dst ConvertToDst(T_Src val)
    {
        return (T_Dst)((object)val);
    }

    public void Add(T_Dst item)
    {
        T_Src val = ConvertToSrc(item);
        _vals.Add(val);
    }

    public void Clear()
    {
        _vals.Clear();
    }

    public bool Contains(T_Dst item)
    {
        return _vals.Contains(ConvertToSrc(item));
    }

    public void CopyTo(T_Dst[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public int Count
    {
        get { return _vals.Count; }
    }

    public bool IsReadOnly
    {
        get { return _vals.IsReadOnly; }
    }

    public bool Remove(T_Dst item)
    {
        return _vals.Remove(ConvertToSrc(item));
    }

    public IEnumerator<T_Dst> GetEnumerator()
    {
        foreach (T_Src cur in _vals)
            yield return ConvertToDst(cur);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public override string ToString()
    {
        return string.Format("Count = {0}", _vals.Count);
    }

    public int IndexOf(T_Dst item)
    {
        return _vals.IndexOf(ConvertToSrc(item));
    }

    public void Insert(int index, T_Dst item)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    public T_Dst this[int index]
    {
        get { return ConvertToDst(_vals[index]); }
        set { _vals[index] = ConvertToSrc(value); }
    }
}
0 голосов
/ 26 августа 2009

используйте это как тело получателя вашей собственности:

List<INail> tempNails = new List<INail>();
foreach (Nail nail in nails)
{
    tempNails.Add(nail);
}
ReadOnlyCollection<INail> readOnlyTempNails = new ReadOnlyCollection<INail>(tempNails);
return readOnlyTempNails;

Это немного хакерское решение, но оно делает то, что вы хотите.

Отредактировано для возврата коллекции ReadOnlyCollection. Обязательно обновите ваши типы в IBucket и Bucket.

0 голосов
/ 26 августа 2009

Существует одно решение, которое может быть не совсем тем, о чем вы просите, но может быть приемлемой альтернативой - вместо этого используйте массивы.

internal sealed class Bucket : IBucket
{
    private Nail[] nails;

    INail[] IBucket.Nails
    {
        get { return this.nails; }
    }

    public Bucket()
    {
        this.nails = new Nail[100];
    }
}

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

0 голосов
/ 26 августа 2009

вы можете использовать расширение Cast

nails.Cast<INail>()

Я не могу проверить это здесь, чтобы предоставить более полный пример, так как мы используем .NET 2.0 на работе (gripe gripe), но у меня был похожий вопрос здесь

0 голосов
/ 26 августа 2009

Какую версию .Net вы используете?

Если вы используете .net 3.0+, вы можете достичь этого только с помощью System.Linq.

Проверьте этот вопрос , который решил его для меня.

...