Reputation: 27250
For instance, I have two classes performing the same function:
class HDD : Disk
{
public HDD RMA()
{
HDD newHDD = RMAService.Exchange(this);
return newHDD;
}
}
class SSD : Disk
{
public SSD RMA()
{
SSD newSSD = RMAService.Exchange(this);
return newSSD;
}
}
What I would like is to implement the RMA function in the base class, but maintain the strongly typed return value, so that the user can be certain by the function signature that they are getting back the type of disk they sent in!
What I have tried so far:
(see solutions below)
This type definition is ugly though. Anyone creating their own Disk class or a reference to a Disk would have a hard time knowing how to correctly use the type.
There's also no way to constrain the type argument to be exactly the class being defined. It just seems odd that there isn't a specialized way of declaring a property or method in a base class where the compile time type is whatever the derived type is.
Upvotes: 2
Views: 157
Reputation: 12214
Not sure if this is what you want, but try separating the (hidden) implementation in the base class from the public interface methods in the derived subclasses.
abstract class Disk //base class
{
protected Disk CreateRMA()
{
Disk newDisk;
newDisk = new RMAService.Exchange(this);
...whatever else needs to be done...
return newDisk;
}
}
class HDD: Disk
{
public HDD RMA()
{
return CreateRMA();
}
}
class SSD: Disk
{
public SSD RMA()
{
return CreateRMA();
}
}
Each derived class's RMA()
method simply calls the base class's CreateRMA()
method and then casts the resulting Disk
object to the derived class type. Each returned value has the proper subtype, and all of the actual work for each RMA()
method is done in the base class method.
Now you can do these:
HDD disk1 = someHDD.RMA();
SSD disk2 = someSSD.RMA();
Addendum
This does not solve the OP's problem, since the call to RMAService.Exchange()
should be specific to (overloaded for) each derived subtype. A different approach keeps that call in the derived types' method, but allows the base class to do all of the rest of whatever initialization work is needed:
// Second approach
abstract class Disk //base class
{
protected Disk CreateRMA(Disk newDisk)
{
...other initialization code...
return newDisk;
}
public abstract Disk RMA(); //not sure if this works
}
class HDD: Disk
{
public override HDD RMA()
{
return CreateRMA(new RMAService.Exchange(this));
}
}
class SSD: Disk
{
public override SSD RMA()
{
return CreateRMA(new RMAService.Exchange(this));
}
}
It's a little more work for the derived classes, but only for the overloaded new
call; all of the rest of the initialization code is kept on the base class method.
Surrender
Okay, this won't work either, because C# does not support covariant return types.
Upvotes: 0
Reputation: 125660
You can make your baseline solution a little more easy-to-read and avoid type check within Disk
constructor using protected
constructor and dynamic
on Exchange
call:
abstract class Disk<T> where T : Disk<T> {
protected Disk()
{
}
public T RMA()
{
return RMAService.Exchange((dynamic)this);
}
}
protected
constructor makes classes like class HDD : Disk<SSD>
fail at compile time and dynamic
delays Exchange
method overload matching decision till runtime, so you'll get the correct one (or error when non fits real this
type).
Upvotes: 1
Reputation: 27250
I just came up with this solution using an extension method, which appears much better. Any reason why this wouldn't work how it appears to work at compile time?
public abstract class Disk
{
public Disk() { }
}
public static class Disk_Extension_Methods
{
public static T RMA<T>(this T oldDisk) where T : Disk
{
T newDisk = RMAService.Exchange(oldDisk)
return newDisk;
}
}
Which allows you to go:
public class HDD : Disk { }
public class SSD : Disk { }
HDD newHDD = someHDD.RMA();
Upvotes: 1
Reputation: 27250
Posting this answer to set a baseline, but as I said, I'm not a fan of this odd "Have a class pass itself as a type parameter to its base class" construct.
abstract class Disk<T> where T : Disk<T> //Ugly recursive type parameter
{
public Disk()
{
if( this.GetType() != typeof(T) ) //Ugly type check
throw new Exception("The type argument must be precisely the " +
"derived class you're defining. Annoying eh?")
}
public T RMA()
{
Disk<T> newDisk = RMAService.Exchange(this)
return (T)newDisk; //Ugly explicit cast
}
}
Which allows you to go:
class HDD : Disk<HDD> { } //Ugly self-referencing type parameter
class SSD : Disk<SSD> { }
HDD newHDD = someHDD.RMA();
Upvotes: 1