Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use C# generics without wildcards? [duplicate]

Over in java I'm pretty used to working with generics and the wildcard. Things like: List<? extends Animal>. This allows you to have a collection of subtypes of Animals and run generic routines on each element (e.g. makeNoise()). I'm trying to accomplish this in C# but am a bit confused, since there's no wildcards.

Domain wise, what we're doing here is working with the SQL SMO libraries to collect scripts out of our database. We've got a base interface type that is extended a number of times to script and collect different objects (table, view, function, etc -- this is the T)

public interface IScripter<T> where T : IScriptable
{
    IList<T> CollectScripts(params...)
}

public abstract class AbstractScripter<T> : IScripter<T> where T : IScriptable
{
    ....
}

public class TableScripter : AbstractScripter<Table>
{
    ....
}

public class ViewScripter : AbstractScripter<View>
{
    ....
}

So far so good. Seems like a perfectly sensible object hierarchy right? Here's what I intended to do, until I found out there's no wildcards:

public class Program
{
    static void Main(string[] args)
    {
        // auto discover all scripter modules, table, view, etc
        IList<Iscripter<? extends IScriptable>> allScripters = GetAllScripterModules(); 
        foreach (IScripter<? extends IScriptable> scripter in allScripters)
        {
            IList<? extends IScriptable> scriptedObjects = scripter.CollectScripts(...);
            // do something with scripted objects
        }
    }
 }

Now since <? extends IScriptable> doesn't exist here, what am I supposed to do instead? I've tried a number of things, generic method, just using the base type, all sorts of nasty casting, but nothing really did the trick.

What would you suggest to replace the IList<Iscripter<? extends IScriptable> piece?

TIA

like image 967
John Newman Avatar asked Mar 27 '13 15:03

John Newman


1 Answers

By using out and only passing T or other covarient T interfaces out from the interface, you can make the interface covariant;

public interface IScripter<out T> where T : IScriptable
{
    IEnumerable<T> CollectScripts(params...)
}

You then can't add to the result, because you cannot use the not covarient IList so add separate interface for when you want to add:

public interface IScripterAddable<T> where T : IScriptable
{
    //either:
    IList<T> CollectScripts(params...)
    //or add just what you need to, this is usually better
    //than exposing the underlying IList - basic encapsulation principles
    void AddScript(...)
}

Then just remove the ? extends.

    // auto discover all scripter modules, table, view, etc
    IList<Iscripter<IScriptable>> allScripters = GetAllScripterModules(); 
    foreach (IScripter<IScriptable> scripter in allScripters)
    {
        IEnumerable<IScriptable> scriptedObjects = scripter.CollectScripts(...);
        // do something with scripted objects
    }
like image 86
weston Avatar answered Oct 07 '22 14:10

weston