简体   繁体   中英

Generic interface implementation: Generic return type and generic input type

I want to create class system to generalize getting and posting data from some data source. I have two methods: GetData and PostData. Both methods should have some kind of input, and GetData should also have return type. I tried to write generic interface and to implement it in "DatabaseSource" class:

public class QueryParameter
{
    public QueryParameter()
    {
        this.Direction = ParameterDirection.Input;
    }

    public string Name { get; set; }
    public object Value { get; set; }
    public ParameterDirection Direction { get; set; }
}

public class InputBase
{
    public InputBase()
    {
        ResultMapping = new Dictionary<string, string>();
        Parameters = new List<QueryParameter>();
    }
    public Dictionary<string, string> ResultMapping { get; set; }
    public List<QueryParameter> Parameters { get; set; }
}
public class DatabaseInput: InputBase
    {
        public string Query { get; set; }
        public DatabaseCommandType CommandType { get; set; }
    }

public interface IDataSource<I> where I: InputBase
{
    IEnumerable<T> GetData<T>(I input);
    void PostData(I Input);
} 

Now i tried to implement interface like this:

public class DatabaseDataSource: IDataSource<DatabaseInput>
{
    public IEnumerable<T> GetData<T>(DatabaseInput Input)
    {
        //implementation
    }

    public void PostData(DatabaseInput Input)
    {
        //implementation
    }
}

But i have a problem when I try to instantiate data source like this:

IDataSource<InputBase> dataSource = new DatabaseDataSource();

I cannot use DatabaseInput because this code is in some kind of factory method and I should be able to instantiate other IDataSource implementations.

In short I want to have Input and Output as generic types, and to constraint Input to match concrete IDataSource implementation.

If I'm understanding this correctly (and it's admittedly not 100% clear to me), your data source needs to define both the input and the output. If you do this:

public IEnumerable<T> GetData<T>(DatabaseInput Input)

Then the actual implementation of that method might vary significantly according to what T is. You don't want a scenario of that method where you're inspecting the type of T and branching your code accordingly.

Perhaps what you want is something like this:

public interface IDataSource<TInput, TOutput> where TInput: InputBase
{
    IEnumerable<TOutput> GetData(TOutput input);
    void PostData(TInput Input);
} 

But even then you've got an interface that's defining two seemingly unrelated operations. (It seems unlikely that TInput can be used both as a query to retrieve data and as a command to post/modify data.)

So perhaps you could break it down further:

public interface IDataSource<TInput, TOutput> where TInput: InputBase
{
    IEnumerable<TOutput> GetData(TOutput input);
}

public interface IDataCommand<TInput> where TInput:InputBase
{
    void PostData(TInput Input);
}

To instantiate it you could use an abstract factory:

public interface IDataSourceFactory<TInput, TOutput>
{
    IDataSource<TInput, TOutput> Create();
    void Release(IDataSource<TInput, TOutput> created);
}

The reason for that is because it avoids the need for your class to call var dataSource = new [whatever]. If you do that then it somewhat defeats the purpose of implementing an interface. Regardless of what interface you implement, as soon as you explicitly call new and create a specific type, your class is coupled to that type , not the interface.

That moves the original problem. What is the implementation of the abstract factory? The good news is that the class that depends on the factory doesn't care what the implementation of the factory is. But you'll still need one. One way to go about it is using a DI container. Windsor is helpful because it provides a pattern for creating abstract factories. This blog post describes how to do that in more detail.

Check out Covariance and Contravariance. https://msdn.microsoft.com/en-us/library/mt654055.aspx

You can use the where constraint, click here for more information about constraints.

where T : class //The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.

where T : <interface name> //The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM