如何将Collection <concretetype>作为Collection <interface>?</interface> </concretetype>返回

时间:2009-08-26 02:35:19

标签: c# generics collections interface .net-2.0

我有一个具体的类,其中包含另一个具体类的集合。我想通过接口公开这两个类,但是我无法弄清楚如何将 Collection&lt; ConcreteType&gt; 成员公开为 Collection&lt; Interface&gt; 成员。< / p>

我目前正在使用.NET 2.0

以下代码导致编译器错误:

  

无法隐式转换类型   'System.Collections.ObjectModel.Collection&LT;美甲GT;'至   'System.Collections.ObjectModel.Collection&LT; INAIL&GT;'

注释的强制转换尝试给出了这个编译错误:

  

无法转换类型   'System.Collections.ObjectModel.Collection&LT;美甲GT;'到
  'System.Collections.ObjectModel.Collection&LT; INAIL&GT;'通过一个   引用转换,装箱转换,拆箱转换,包装   转换或null类型转换。

有没有办法将具体类型的集合公开为接口集合,还是需要在接口的getter方法中创建新集合?

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 个答案:

答案 0 :(得分:6)

C#3.0泛型是不变的。如果不创建新对象,则无法做到这一点。 C#4.0引入了安全的协方差/逆变,无论如何都不会改变读/写集合(你的情况)。

答案 1 :(得分:6)

将指甲定义为

Collection<INail>

答案 2 :(得分:1)

为什么不将它作为一个接口返回,只需在接口中拥有所有公共方法,这样你就没有这个问题了,如果你以后决定返回另一种类型的Nail类,那么它会正常工作

答案 3 :(得分:0)

您使用的是什么版本的.Net?

如果您使用的是.net 3.0+,则只能使用System.Linq实现此目的。

查看this question,为我解决了这个问题。

答案 4 :(得分:0)

有一种解决方案可能不是您所要求的,但可能是可接受的替代方案 - 而是使用数组。

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

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

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

(如果你最终做这样的事情,请记住这个框架设计指南说明:通常不应将数组作为属性公开,因为它们通常在返回给调用者之前被复制并且复制是一项昂贵的操作。在一个无辜的财产里面做。)

答案 5 :(得分:0)

使用它作为你的属性getter的主体:

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

这有点像一个hacky解决方案,但它做你想要的。

编辑返回ReadOnlyCollection。确保在IBucket和Bucket中更新您的类型。

答案 6 :(得分:0)

您可以添加一些泛型。适合更好,更强烈耦合。

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 s(而非Nail s)时,您才可以尝试以下方法。但有一个问题。一方面,您表示要在private Collection<Nail>课程中使用Bucket而不是Collection<INail>,因为您不想意外添加INail中的其他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>();
    }
}

答案 7 :(得分:-1)

您可以使用Cast扩展程序

nails.Cast<INail>()

我无法在此测试它以提供更全面的示例,因为我们正在使用.NET 2.0(抱抱抱怨),但我确实有类似的问题here

答案 8 :(得分:-1)

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); }
    }
}