Reputation: 221
How it can be fixed? Why I can not to use this constructions?
using System;
public class Program
{
public interface IReadParamModel{ }
public interface IReadResultModel{ }
public interface IWriteParamModel{ }
public interface IWriteResultModel{ }
public interface IDataReader<TParam, TResult> where TParam : IReadParamModel where TResult : IReadResultModel
{
TResult Get(TParam param);
}
public interface IDataWriter<TParam, TResult> where TParam : IWriteParamModel where TResult : IWriteResultModel
{
TResult Write(TParam param);
}
public abstract class BaseReportService<TReader, TWriter>
where TReader : IDataReader<IReadParamModel, IReadResultModel>
where TWriter : IDataWriter<IWriteParamModel, IWriteResultModel>
{
TWriter writer;
TReader reader;
}
public class ReaderParamModel : IReadParamModel { }
public class ReadResultModel : IReadResultModel { }
public class WriteParamModel : IWriteParamModel { }
public class WriteResultModel : IWriteResultModel { }
public class DataReader : IDataReader<ReaderParamModel, ReadResultModel>
{
public ReadResultModel Get(ReaderParamModel param) { return null; }
}
public class DataWriter : IDataWriter<WriteParamModel, IWriteResultModel>
{
public IWriteResultModel Write(WriteParamModel param){ return null; }
}
public class ReportService : BaseReportService<DataReader, DataWriter>
{
}
}
Compilation error (line 46, col 15): The type 'Program.DataReader' cannot be used as type parameter 'TReader' in the generic type or method 'Program.BaseReportService'.
There is no implicit reference conversion from 'Program.DataReader' to 'Program.IDataReader'.Compilation error (line 46, col 15): The type 'Program.DataWriter' cannot be used as type parameter 'TWriter' in the generic type or method 'Program.BaseReportService'.
There is no implicit reference conversion from 'Program.DataWriter' to 'Program.IDataWriter'.
Upvotes: 2
Views: 141
Reputation: 13448
The problem is, that IDataReader<IReadParamModel, IReadResultModel>
and IDataReader<ReaderParamModel, ReadResultModel>
are incompatible types. In order to make them compatible, co-/contravariance would be needed, but with TResult Get(TParam param);
, TParam
would be contravariant and TResult
would be covariant. This means, there is no way to make the two interfaces compatible with their current usage.
The choices are, to use the interfaces directly if access to the implementation properties is not required or to use the concrete types as additional generic parameters. The following code contains three sections that demonstrate the different designs, based on the co-/contravariant IDataReader
interface.
The code is restricted to the Reader
part, since the examples for reader and writer are quite similar. The Test
method is used to highlight some differences in the actually available types at different inheritance levels.
public interface IReadParamModel { }
public interface IReadResultModel { }
public class ReaderParamModel : IReadParamModel { }
public class ReadResultModel : IReadResultModel { }
public interface IDataReader<in TParam, out TResult>
where TParam : IReadParamModel
where TResult : IReadResultModel
{
TResult Get(TParam param);
}
// First variant - much interface usage
public class DataReader_1 : IDataReader<IReadParamModel, ReadResultModel>
{
public ReadResultModel Get(IReadParamModel param) { return null; }
}
public abstract class BaseReportService_1<TReader>
where TReader : IDataReader<IReadParamModel, IReadResultModel>
{
protected TReader reader;
// input is interface, reader.Get result is interface
protected virtual IReadResultModel Test(IReadParamModel param)
{
var result = reader.Get(param);
return result;
}
}
public class ReportService_1 : BaseReportService_1<DataReader_1>
{
// input is interface, reader.Get result is concrete class
protected override IReadResultModel Test(IReadParamModel param)
{
var result = reader.Get(param);
return result;
}
}
// Second variant - less interface usage, more generic parameters
public class DataReader_2 : IDataReader<ReaderParamModel, ReadResultModel>
{
public ReadResultModel Get(ReaderParamModel param) { return null; }
}
public abstract class BaseReportService_2<TReader, TReaderParam>
where TReader : IDataReader<TReaderParam, IReadResultModel>
where TReaderParam : IReadParamModel
{
protected TReader reader;
// input is concrete class, reader.Get result is interface
protected virtual IReadResultModel Test(TReaderParam param)
{
var result = reader.Get(param);
return result;
}
}
public class ReportService_2 : BaseReportService_2<DataReader_2, ReaderParamModel>
{
// input is concrete class, reader.Get result is concrete class
protected override IReadResultModel Test(ReaderParamModel param)
{
var result = reader.Get(param);
return result;
}
}
// Third variant - fully parameterized
public class DataReader_3 : IDataReader<ReaderParamModel, ReadResultModel>
{
public ReadResultModel Get(ReaderParamModel param) { return null; }
}
public abstract class BaseReportService_3<TReader, TReaderParam, TReadResult>
where TReader : IDataReader<TReaderParam, TReadResult>
where TReaderParam : IReadParamModel
where TReadResult : IReadResultModel
{
protected TReader reader;
// input is concrete class, reader.Get result is concrete class
protected virtual TReadResult Test(TReaderParam param)
{
var result = reader.Get(param);
return result;
}
}
public class ReportService_3 : BaseReportService_3<DataReader_3, ReaderParamModel, ReadResultModel>
{
// input is concrete class, reader.Get result is concrete class
protected override ReadResultModel Test(ReaderParamModel param)
{
var result = reader.Get(param);
return result;
}
}
If you need the concrete types for input and output (like in the 3rd example), you should check, if you really need to specify the reader type for the ReportService.
// Fourth variant - decoupled
// the reader is not really needed for this example...
public class DataReader_4 : IDataReader<ReaderParamModel, ReadResultModel>
{
public ReadResultModel Get(ReaderParamModel param) { return null; }
}
public abstract class BaseReportService_4<TReaderParam, TReadResult>
where TReaderParam : IReadParamModel
where TReadResult : IReadResultModel
{
// reader is interface, can be assigned from DataReader_4 or different implementations
protected IDataReader<TReaderParam, TReadResult> reader;
// input is concrete class, reader.Get result is concrete class
protected virtual TReadResult Test(TReaderParam param)
{
var result = reader.Get(param);
return result;
}
}
public class ReportService_4 : BaseReportService_4<ReaderParamModel, ReadResultModel>
{
// input is concrete class, reader.Get result is concrete class
protected override ReadResultModel Test(ReaderParamModel param)
{
var result = reader.Get(param);
return result;
}
}
Upvotes: 3