Reputation: 1258
Let's say I have the following:
public class DataType1
{
public DataType1() { }
public string Read() { return "dt1"; }
}
public class DataType2
{
public DataType2() { }
public string Read() { return "dt2"; }
}
And a utility class:
public class Logger<T>
{
public Logger() { }
}
Then, in my main app:
class Program
{
static void Main(string[] args)
{
var test1 = new Logger<DataType1>();
test1.Read(); // I want this to equal "dt1";
var test2 = new Logger<DataType2>();
test2.Read(); // I want this to equal "dt2";
}
}
I realize that I'm trying to do a type of covariance. However, I cannot have DataType1/DataType2 inherit from Logger - that would be against architecture (ie. DataType1/DataType2 are at the DAL, and the programmers are required to go through the Logger to get to the DAL).
Any thoughts on how I could accomplish this?
Thanks.
Upvotes: 0
Views: 135
Reputation: 460
If you cant use interface, try using lambda exprestion:
public class Logger<T> where T : new()
{
private Func<T, string> _readFunc;
private T _member;
public Logger(Func<T, string> readFunc)
{
_readFunc = readFunc;
_member = new T();
}
// Use this if you already have an instance of your data type
public Logger(Func<T, string> readFunc, T member)
{
_readFunc = readFunc;
_member = member;
}
public string Read()
{
return _readFunc(_member);
}
}
and then in your app:
static void Main()
{
var test1 = new Logger<DataType1>(t => t.Read());
test1.Read(); // Will be equal "dt1";
var test2 = new Logger<DataType2>(t => t.Read());
test2.Read(); // Will be equal "dt2";
}
Upvotes: 0
Reputation: 4434
As @leppie said if you would add a constraint on the generic utility class declaration. It could solve the issue. You would need to inject dependency on your Logger class through the constructor or the use of properties.
Let's say for example you have the following interface:
public interface IReader
{
public String Read();
}
And we would have DataType1 and DataType2 looking like
public class DataType1:IReader
{
public String Read()
{
return "dt1";
}
}
Your utility class may turn into something like:
public class Logger<T> where T:IReader
{
private T dataTypeInstance;
public Logger(T dataTypeInstance)
{
this.dataTypeInstance = dataTypeInstance;
}
public String Read()
{
return dataTypeInstance.Read();
}
}
And its Read method would simply invoke the Read method of your DataType class.
We can then, get instances of DataType1 and DataType2 from some kind of factory and test with something like:
var test1 = new Logger<DataType1>(dataType1);
var test2 = new Logger<DataType2>(dataType2);
test1.Read(); //will be dt1
test2.Read(); //will be dt2
Alternatively instead of using dependency injection via a constructor or a property in your utility class you may use reflection and properties files or (database - stored configuration ) along with the is
operator to get an instance of the right DataType to use.
Upvotes: 0
Reputation: 29836
Use an interface:
public interface IDataType
{
string Read();
}
public class DataType1 : IDataType
{
public DataType1() { }
public string Read() { return "dt1"; }
}
public class DataType2 : IDataType
{
public DataType2() { }
public string Read() { return "dt2"; }
}
public class Logger<T> where T : IDataType, new()
{
IDataType dataType { get; set; }
public Logger() {
dataType = new T();
}
public string Read()
{
return dataType.Read();
}
}
class Program
{
static void Main(string[] args)
{
var test1 = new Logger<DataType1>();
test1.Read(); // I want this to equal "dt1";
var test2 = new Logger<DataType2>();
test2.Read(); // I want this to equal "dt2";
}
}
Btw, I think a better practice would be to drop the generics:
public interface IDataType
{
string Read();
}
public class DataType1 : IDataType
{
public DataType1() { }
public string Read() { return "dt1"; }
}
public class DataType2 : IDataType
{
public DataType2() { }
public string Read() { return "dt2"; }
}
public class Logger
{
IDataType dataType { get; set; }
public Logger(IDataType dt) {
dataType = dt;
}
public string Read()
{
return dataType.Read();
}
}
class Program
{
static void Main(string[] args)
{
var dt1 = new DataType1();
var test1 = new Logger(dt1);
test1.Read(); // I want this to equal "dt1";
var dt2 = new DataType2();
var test2 = new Logger(dt2);
test2.Read(); // I want this to equal "dt2";
}
}
Upvotes: 4
Reputation: 14512
Use interfaces:
public class DataType1 : IReadable
{
public DataType1() { }
public string Read() { return "dt1"; }
}
public class DataType2 : IReadable
{
public DataType2() { }
public string Read() { return "dt2"; }
}
public interface IReadable
{
string Read();
}
And enforce that the generic type implements this interface:
public class Logger<T> where T : IReadable
You'll have to get a reference to the instance which performs the Read
operation, and then you can actually have a delegating Read
action on the logger itself:
public class Logger<T> where T : IReadable
{
private readonly T _readable;
public Logger<T>(T readable)
{
this._readable = readable;
}
public string Read()
{
return this._readable.Read();
}
}
And usage would be:
var dt1 = new DataType1();
var dt2 = new DataType2();
var logger1 = new Logger<DataType1>(dt1);
var logger2 = new Logger(dt2); // can omit generic noise!
Console.WriteLine(logger1.Read()); // "dt1"
Console.WriteLine(logger2.Read()); // "dt2"
If you want to avoid having to create the instance and pass it to the logger, you can instantiate it inside the logger, but you'll have to add the new()
constraint, which means that there is a public constructor that takes no parameters, and you can even have the logger implement IReadable
itself:
public class Logger<T> : IReadable where T : IReadable, new()
{
private readonly T _readable;
public Logger<T>()
{
this._readable = new T();
}
public string Read()
{
return this._readable.Read();
}
}
And usage would be:
var logger1 = new Logger<DataType1>();
Console.WriteLine(logger1.Read()); // "dt1"
Upvotes: 1