Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a class or interface for a complex generic type

I am trying to create an alias for a type of list of list of object. Specifically, I want to shorten all the typing I have to do for this type:

IReadOnlyList<IReadOnlyList<MyObject>>

My attempt is demonstrated here:

using System.Collections.Generic;

namespace MyApp
{
    class Program
    {
        public class MyObject
        {
            public static IMyCollection GetCollection()
            {
                var a = new List<MyObject>();
                a.Add(new MyObject());

                var b = new List<IReadOnlyList<MyObject>>();
                b.Add(a.AsReadOnly());

                return b.AsReadOnly();
            }
        }

        public interface IMyCollection : IReadOnlyList<IReadOnlyList<MyObject>>
        {
        }

        static void Main(string[] args)
        {
            var collection = MyObject.GetCollection();
        }
    }
}

Unfortunately, this won't compile. There error is:

    Cannot implicitly convert type
 'System.Collections.ObjectModel.ReadOnlyCollection<System.Collections.Generic.IReadOnlyList<MyApp.Program.MyObject>>'
 to 'MyApp.Program.IMyCollection'.
 An explicit conversion exists (are you missing a cast?)

OK, so I'm close. Perhaps explicitly casting? So I change the return statement in GetCollection to

return (IMyCollection)b.AsReadOnly();

That compiles, albeit with a resharper warning: Suspicious cast: there is no type in the solution which is inherited from both 'System.Collections.ObjectModel.ReadOnlyCollection>' and 'MyApp.Program.IMyCollection'

And at runtime, I get an invalid cast exception: Unable to cast object of type 'System.Collections.ObjectModel.ReadOnlyCollection1[System.Collections.Generic.IReadOnlyList1[MyApp.Program+MyObject]]' to type 'IMyCollection'.

OK, I can accept all that. I'm the last person to ask about stuff like covariance and contravariance and stuff like that. But surely there's a way to define and create an object with a short name to stand in for a really long named datatype.

How can I create a type with a really long name and cast to a type with a really short name?

UPDATE: A co-worker suggested using a using statement.

using IMyCollection= System.Collections.Generic.IReadOnlyList<System.Collections.Generic.IReadOnlyList<MyApp.Program.MyObject>>;

While that would work, it then becomes necessary to do that in every file that uses IMyCollection. Not exactly what I would consider a solution to my goal.

like image 440
arch-imp Avatar asked Mar 18 '23 04:03

arch-imp


1 Answers

How badly do you want this?

You can manually implement your own wrapper class.

public interface IMyCollection : IReadOnlyList<IReadOnlyList<MyObject>>
{
}

public class MyCollectionImpl : IMyCollection
{
    private readonly IReadOnlyList<IReadOnlyList<MyObject>> _wrappedCollection;
    public MyCollectionImpl(IReadOnlyList<IReadOnlyList<MyObject>> wrappedCollection)
    {
        _wrappedCollection = wrappedCollection;
    }

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

    public IReadOnlyList<MyObject> this[int index]
    {
        get 
        {
            return _wrappedCollection[index];
        }
    }

    public IEnumerator<IReadOnlyList<MyObject>> GetEnumerator()
    {
        return _wrappedCollection.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _wrappedCollection.GetEnumerator();
    }
}

Then you simply create an instance of this:

 public class MyObject
    {
        public static IMyCollection GetCollection()
        {
            var a = new List<MyObject>();
            a.Add(new MyObject());

            var b = new List<IReadOnlyList<MyObject>>();
            b.Add(a.AsReadOnly());

            return new MyCollectionImpl(b.AsReadOnly());
        }
    }

This seems like a lot of extra work, but I would actually consider this a refactoring step.

I believe that the need to pass around types made up of complex set of generic parameters, is actually a bad smell in your code.

Ask yourself, what are you actually using IMyCollection for? Would you be able to add some specialized methods to this interface to make it easier to use?

Once you've created your own MyCollectionImpl class you can slowly add a number of methods to your IMyCollection interface to simplify it's usage. At some point, you might even get to the stage where you can stop exposing the <IReadonlyList<IReadonlyList<MyObject>> interface.

like image 179
Andrew Shepherd Avatar answered Apr 02 '23 13:04

Andrew Shepherd