Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing generic methods from an interface using another interface

I'm attempting to create a common interface which will allow me n methods of interacting with a database. I want my business application to be able to instantiate any of the connection methodologies and be assured the interface is identical.

Here's a simplified version of what I'm trying now.

Database Interface where IElement is another interface which would define a table.

public interface IDatabase
{
    void setItem( IElement task );  //this works fine
    List<T> listTasks<T>() where T : IElement; // this doesn't
}

IElement interface:

public interface IElement
{
    int id { get; set; }
}

Implementation of IElement:

public class TaskElement: IElement
{
    public int id { get; set; }
    public string name {get; set; }
}

Implementation of IDatabase:

public class SQLiteDb: IDatabase
{
    public SqLiteDb( SQLiteConnection conn )
    {
        database = conn;
    }

    public void setItem( IElement task )
    {
        // works fine when passed a new TaskElement() which is an implementation of IElement.
        database.Insert( task ); 
    }

    //it all goes off the rails here
    public List<T> listItems<T>() where T : IElement
    {
        var returnList = new List<IElement>

        foreach (var s in database.Table<TaskElement>())
        { returnList.Add(s); }

        return returnList;
    }

I've tried a lot of variations on this but each one gives me a new issue. Here, for instance, there are two errors.

1)

The type arguments for method 'SQLiteDb.listTasks<T>()' cannot be inferred from the usage. Try specifying the type arguments explicitly.

2)

Cannot implicitly convert type 'System.Collections.Generic.List<TaskElement>' to 'System.Collections.Generic.List<T>'

I've tried changing the method to use an explicit type but have had issues there. If I use IElement (my generic interface for all elements )I can't return a list of TaskElement objects (my implementation of IElement) as it doesn't match the return type (List<IElement>) and if I change the return type to List<TaskElement> I'm not longer implementing the interface.

It's worth noting that I can easily get this to work if I stop using the interface and generics, but this seems like an ideal situation to use an interface. Maybe I'm trying to hard to cram a lot of stuff into an interface when another application (like direct inheritance) might be better?

Question

How can I implement an interface with a generic return value while limiting the types which can be returned to only implementations of another interface.

like image 428
Nigel DH Avatar asked Aug 01 '16 15:08

Nigel DH


Video Answer


2 Answers

Let's look closely at your implementation of listItems:

public List<T> listItems<T>() where T : IElement
{
    var returnList = new List<IElement>

    foreach (var s in database.Table<TaskElement>())
    { returnList.Add(s); }

    return returnList;
}

What you've done here is written a method where the caller is allowed to ask for any type they want in the list as long as that type implements IElement. But the code in your method doesn't give them a list of the type they want, it gives them a list of IElement. So it's violating the contract.

But the real root of your problem is database.Table<TaskElement>(). That can only ever give you instances of TaskElement. You need to make that T, but to do that you need an additional generic constraint:

public List<T> listItems<T>() where T : IElement, new
{
    var returnList = new List<T>

    foreach (var s in database.Table<T>())
    { 
        returnList.Add(s); 
    }

    return returnList;
}

This is because database.Table<T> has a new constraint, which means that it can only be given types that have a zero-parameter constructor (because that method is going to create instances of the given class).

like image 147
Kyle Avatar answered Sep 28 '22 03:09

Kyle


I belive it should be something like this

public List<T> listItems<T>() where T : IElement
    {
        var returnList = new List<T>

        foreach (var s in database.Table<T>())
        { returnList.Add(s); }

        return returnList;
    }
like image 26
MikkaRin Avatar answered Sep 28 '22 03:09

MikkaRin