jameswelle
jameswelle

Reputation: 1397

How can you return a Collection<ConcreteType> as a Collection<Interface>?

I have a concrete class that contains a collection of another concrete class. I would like to expose both classes via interfaces, but I am having trouble figuring out how I can expose the Collection<ConcreteType> member as a Collection<Interface> member.

I am currently using .NET 2.0

The code below results in a compiler error:

Cannot implicitly convert type 'System.Collections.ObjectModel.Collection<Nail>' to 'System.Collections.ObjectModel.Collection<INail>'

The commented attempt to cast give this compiler error:

Cannot convert type 'System.Collections.ObjectModel.Collection<Nail>' to
'System.Collections.ObjectModel.Collection<INail>' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion.

Is there any way to expose the collection of concrete types as a collection of interfaces or do I need to create a new collection in the getter method of the interface?

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

Upvotes: 3

Views: 2437

Answers (9)

nawfal
nawfal

Reputation: 73233

You can add some generics. Fits better, more strongly coupled.

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

This way the Collection<Nail> you return from Bucket class can only ever hold Nails. Any other INail wont go into it. This may or may not be better depending on what you want.


Only if you want Collection<INail> (the interface property) you return from Bucket to hold other INails (than Nails) then you may try the below approach. But there is a problem. On one side you say you want to use a private Collection<Nail> in Bucket class and not a Collection<INail> because you dont want to accidentally add other INails from Bucket class into it but on the other side you will have to add other INails from outside of Bucket class. This is not possible on the same instance. Compiler stops you from accidentally adding any INail to a Collection<Nail>. One way is to return a different instance of Collection<INail> from your Bucket class from the existing Collection<Nail>. This is less efficient, but could be the semantics you are after. Note that this is conceptually different from above

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

Upvotes: 0

skevar7
skevar7

Reputation: 1005

C# doesn't support generic collections covariance (it's only supported for arrays). I use an adapter class in such cases. It just redirects all calls to the actual collection, converting values to the required type (doesn't require copying all list values to the new collection). Usage looks like this:

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

Upvotes: -1

RCIX
RCIX

Reputation: 39457

use this as the body of your property getter:

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

That is a tad bit of a hacky solution but it does what you want.

Edited to return a ReadOnlyCollection. Make sure to update your types in IBucket and Bucket.

Upvotes: 0

bobbymcr
bobbymcr

Reputation: 24167

There is one solution that might not be quite what you are asking for but could be an acceptable alternative -- use arrays instead.

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

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

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

(If you end up doing something like this, keep in mind this Framework Design Guidelines note: generally arrays shouldn't be exposed as properties, since they are typically copied before being returned to the caller and copying is an expensive operation to do inside an innocent property get.)

Upvotes: 0

johnc
johnc

Reputation: 40223

you could use the Cast extension

nails.Cast<INail>()

I can't test it here to provide a more comprehensive example, as we are using .NET 2.0 at work (gripe gripe), but I did have a similar question here

Upvotes: -1

kevindaub
kevindaub

Reputation: 3363

What version of .Net are you using?

If you are using .net 3.0+, you can only achieve this by using System.Linq.

Check out this question, which solved it for me.

Upvotes: 0

James Black
James Black

Reputation: 41858

Why not just return it as an interface, just have all your public methods in the interface, that way you don't have this problem, and, if you later decide to return another type of Nail class then it would work fine.

Upvotes: 1

ChaosPandion
ChaosPandion

Reputation: 78282

Just define nails as

Collection<INail>

Upvotes: 6

Mehrdad Afshari
Mehrdad Afshari

Reputation: 422112

C# 3.0 generics are invariant. You can't do that without creating a new object. C# 4.0 introduces safe covariance/contravariance which won't change anything about read/write collections (your case) anyway.

Upvotes: 6

Related Questions