views:

89

answers:

5

Take the following example (created purely to demonstrate the point). What I am trying to do is isolate one part of the system from another and only want to expose a specific subset of the functionality externally from the assembly while internally working against the full objects methods.

This code compiles, but I get an invalid cast exception at runtime. It feels like this should work but unfortunately it does not.

Can anybody suggest an elegant solution to this problem?

UPDATED: Based on comments I have changed this example to better demonstrate the issue, I also in the sample now show the solution that worked for me...

    using System.Collections.Generic;

    namespace Test
    {
        public class PeopleManager
        {
            List<Person> people = new List<Person>();

            public PeopleManager()
            {
            }

            public void CreatePeople()
            {               
                people.Add(new Person("Joe", "111 aaa st"));
                people.Add(new Person("Bob", "111 bbb st"));
                people.Add(new Person("Fred", "111 ccc st"));
                people.Add(new Person("Mark", "111 ddd st"));                
            }

            public IList<IName> GetNames()
            {
                /* ERROR
                 * Cannot convert type 'System.Collections.Generic.List<Test.Person>' 
                 * to 'System.Collections.Generic.List<Test.IName>' c:\notes\ConsoleApplication1\Program.cs
                 */

                return (List<IName>) people; // <-- Error at this line

                // Answered my own question, do the following

                return people.ConvertAll(item => (IName)item);
            }
        }

        public interface IName
        {
            string Name { get; set; }
        }

        internal class Person : IName
        {
            public Person(string name, string address)
            {
                this.Name = name;
                this.Address = address;
            }

            public string Name
            {
                get;
                set;
            }

            public string Address
            {
                get;
                set;
            }
        }
    }
+6  A: 
IList<IName> restricted = people.Cast<IName>().ToList(); 
Elisha
Marked as answer - important to not this is only in .net 3.5 and above. The ConvertAll answer I used in my solution works in .net 2.0 and above.
Steve Sheldon
+3  A: 

Person inherits from IName but List<Person> does not inherit from List<IName>. Imagine if it did: you'd be able to cast a List<Person> to its superclass, List<IName>, then add an instance of IName that was not an instance of Person!

Just make your List<Person> a List<IName>--that should be sufficient.

Jonathan Grynspan
If I do that, in my internal classes I would have to keep casting the list, thanks though
Steve Sheldon
this is the correct answer :)
vulkanino
@vulkanino, why do you think this is the "correct" answer? Do you mean to suggest the other solutions somehow won't work?
Kirk Woll
Other solutions *may* work, but jonathan's answer is an example of good OO design, while *any* cast is not! I know this is a strong assertion, but I think it is true. In you example a Person IS a IName, thanks to polymorphism, and that's one of the reasons Interfaces are so good and so much used. You should strive for a design that won't force you to examine a type in runtime (typeof) or try to cast between types. Instead, you should try to always program to interfaces, not to object instances.
vulkanino
vulkanino: A `Person` *is* an `IName`, but it also has an `Address`. When somebody who needs an `Address` gets his `List<IName>`, they'll have to cast. So how is that design better?
Gabe
His code doesn't indicate that the list is used outside of a particular function; only that it needs to be used as a `List<IName>`. If he needs to treat the elements as `Person` instances, he should have a `List<Person>`, but the code and his comments suggest this is not the case for this particular question.
Jonathan Grynspan
The true use case I have is I have a complex class with lot of functions that gets used within the assembly, when exposed from the assembly, I only want the caller to be able to perform a very limitied subset of operations on what will be a recursive tree structure. All the internal workings of the nodes are of no interest to the caller. Contrary to other posts here, I believe it to be good OO design to only expose the limited interface from the assembly so users of the data can't shoot themselves in the foot. I could makea limited clone but this has perf issues.
Steve Sheldon
Then you should expose as your interface the bare minimum that fulfills your class's contract. If your class is responsible for maintaining an (ordered or unordered) set of `Person` instances, then it should be doling out `IList<Person>` or even `ICollection<Person>`. *Within* your class, you can feel free to use concrete types--`List<Person>` or `List<IName>` or whatever you need for the particular situation.
Jonathan Grynspan
+1  A: 

Answered my own question but thought I would post in case anybody else runs into this in the future. At the line where the error occurs you can actually do this which is pretty nifty:

List<IName> restricted = people.ConvertAll(item => (IName) item); 
Steve Sheldon
I prefer Elisha's answer. Operates on IEnumerable<T> so is useful in far more situations.
Kirk Woll
good to know :) thanks - here I wanted to use findall so IEnumerable didn't work but generally I agree
Steve Sheldon
@Kirk - just good to know, the Cast is .net 3.5 and above, ConvertAll is .net 2.0. My project has to be 2.0 so I couldn't use Cast
Steve Sheldon
A: 

You can create an extension method that is generic and takes a conversion function:

public static List<TTarget> TransformList<TSource, TTarget>(this IEnumerable<TSource> sourceItems, Func<TSource, TTarget> transformFunction)
{
    List<TTarget> targetItems = new List<TTarget>();

    foreach (TSource sourceItem in sourceItems)
    {
        targetItems.Add(transformFunction(sourceItem));
    }

    return targetItems;
}

This can then be called in the following way:

IList<IName> restricted = people.TransformList(person => (IName)person);
m_arnell
A: 

I would create a class to wrap the one collection (for performance) rather than make a copy like every other solution on the page.

public class CovariantList : IList where TType:TBase,class { private IList _innerList;

public int Count { get { return this._innerList.Count; } }

public TBase this[int index] { get { return this._innerList[index]; } set { TType type = value as TType; if(type!=null) { ... } } ...

Amir