Эффективно вернуть IList <Interface>из списка <T>(избегать приведения из списка <T>в список <I>) - PullRequest
0 голосов
/ 25 мая 2018

У меня есть следующий код:

public interface ISomeObject
{
     IList<ISomeObject> Objects { get; }
}
public class SomeObject : ISomeObject
{
    public SomeObject()
    {
        Objects = new List<SomeObject>();
    }
    public List<SomeObject> Objects
    {
         get;
         set;
    }
    IList<ISomeObject> ISomeObject.Objects
    {    
        get 
        {
            // What to do here?
            // return Objects; // This doesn't work
            return Objects.Cast<ISomeObject>().ToList(); // Works, but creates a copy each time.
         }
    }

SomeObject имеет открытое свойство Objects, которое возвращает список типа класса.Клиенты, знающие, что этот тип класса может использовать это, чтобы сделать все, что они хотят.Клиенты, знающие только о ISomeObject, могут использовать свойство Objects только для получения IList<ISomeObject>.Поскольку не разрешено разыгрывать List<SomeObject> в IList<ISomeObject> (из-за проблемы яблока и банана ), мне нужен способ конвертировать это.Способ по умолчанию с использованием Cast.ToList () работает, но имеет недостаток, заключающийся в том, что он создает новый список при каждой оценке свойства, что может быть дорогостоящим.Изменение ISomeObject.Objects для возврата IEnumerable<ISomeObject> имеет другой недостаток: клиент больше не может использовать индексацию (что весьма актуально в моем случае использования).А использование многократного вызова Linq ElementAt () обходится дорого, когда используется в IEnumerable.

Кто-нибудь знает, как избежать любой из этих проблем?(конечно, сделать SomeObject известным везде - не вариант).

Ответы [ 3 ]

0 голосов
/ 25 мая 2018

Изменение ISomeObject.Objects для возврата IEnumerable<ISomeObject> имеет другой недостаток: клиент больше не может использовать индексирование (что весьма актуально в моем случае использования).

Индексирование поддерживается не только интерфейсом IList<T>, но и интерфейсом IReadOnlyList<T>.Поскольку IReadOnlyList<T> не позволяет модификацию, она может быть (и есть) ковариантной, как IEnumerable<T>.

Итак, просто измените тип возвращаемого значения на IReadOnlyList<ISomeObject> и верните исходный список.

Конечно, ничто не мешает вызывающему преобразовать результат в List<SomeObject>, но предполагается, что вызывающий в любом случае имеет полный доступ к этому списку, так что угрозы безопасности нет.

0 голосов
/ 25 мая 2018

Возможно, вы захотите попытаться инкапсулировать ваш List<SomeObject>, сделав его подробностью реализации, и вместо этого вернуть IReadOnlyList<SomeObject>.Тогда от SomeObject до ISomeObject приведение может быть ненужным в реализации интерфейса, а также из-за дисперсии IReadOnlyList - вы сможете вернуть Objects как IReadOnlyList<ISomeObject>.

Затем просто добавьте несколькооперации для изменения вашего базового списка, такие как Add или Remove, к типу контейнера, если они необходимы.

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

0 голосов
/ 25 мая 2018

Вы можете / должны реализовать класс, подобный ReadOnlyCollection<T>, чтобы действовать как прокси.Учитывая, что он будет доступен только для чтения, он может быть «ковариантным» (не языковым, а логически, что означает, что он может проксировать TDest, который является подклассом / интерфейсом TSource), а затем throw NotSupportedException() для всехметоды записи.

Примерно так (код не проверен):

public class CovariantReadOlyList<TSource, TDest> : IList<TDest>, IReadOnlyList<TDest> where TSource : class, TDest
{
    private readonly IList<TSource> source;

    public CovariantReadOlyList(IList<TSource> source)
    {
        this.source = source;
    }

    public TDest this[int index] { get => source[index]; set => throw new NotSupportedException(); }

    public int Count => source.Count;

    public bool IsReadOnly => true;

    public void Add(TDest item) => throw new NotSupportedException();

    public void Clear() => throw new NotSupportedException();

    public bool Contains(TDest item) => IndexOf(item) != -1;

    public void CopyTo(TDest[] array, int arrayIndex)
    {
        // Using the nuget package System.Runtime.CompilerServices.Unsafe
        // source.CopyTo(Unsafe.As<TSource[]>(array), arrayIndex);
        // We love to play with fire :-)

        foreach (TSource ele in source)
        {
            array[arrayIndex] = ele;
            arrayIndex++;
        }
    }

    public IEnumerator<TDest> GetEnumerator() => ((IEnumerable<TDest>)source).GetEnumerator();

    public int IndexOf(TDest item)
    {
        TSource item2 = item as TSource;

        if (ReferenceEquals(item2, null) && !ReferenceEquals(item, null))
        {
            return -1;
        }

        return source.IndexOf(item2);
    }

    public void Insert(int index, TDest item)
    {
        throw new NotSupportedException();
    }

    public bool Remove(TDest item)
    {
        throw new NotSupportedException();
    }

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

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

Использовать как:

IList<string> strs = new List<string>();
IList<object> objs = new CovariantReadOlyList<string, object>(strs);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...