I have Bill
s and Receipt
s. Both types have a property called Lines
, but Receipt.Lines
is full of ReceiptLine
s and Bill.Lines
is full of BillLine
s. I'd like them to both inherit from a class called Document
with a property Lines
that's full of DocumentLine
s so that I can occasionally pass them to functions that operate on Document
s, but I don't want to have to myReceipt.Lines.Select(line => (ReceiptLine)line)
each time I am specifically using a Bill
or Receipt
. Is there an elegant way to do this?
Note that the following attempt results in CS1503 Argument 1: cannot convert from 'Receipt' to 'Document<DocumentLine>'
void Main()
{
var something = new Receipt();
DoStuff(something);
}
public void DoStuff(Document<DocumentLine> document) { }
public abstract class DocumentLine { }
public class BillLine : DocumentLine { }
public class ReceiptLine : DocumentLine { }
public abstract class Document<TDocLine> where TDocLine : DocumentLine
{
public List<TDocLine> Lines { get; set; }
}
public class Bill : Document<BillLine> { }
public class Receipt : Document<ReceiptLine> { }
You could use a generic type to define the List item type, like so:
interface DocumentLine { }
class BillLine : DocumentLine { }
class ReceiptLine : DocumentLine { }
class Document<T> where T : DocumentLine
{
public List<T> Lines { get; set; }
}
class Bill : Document<BillLine> { }
class Receipt : Document<ReceiptLine> { }
Edit: What the new implied question is referring to is called ' Generic Covariance '. In C# generic covariance is limited to interface and delegate types [see out keyword (generic modifier) ].
Instead, to get the behavior you want, you'll have to carry the generic variable as generic with conditions, rather than a fixed covariant type.
public void DoStuff<T>(Document<T> document) where T : DocumentLine { }
Note that you cannot change a type when overriding, but you can make the line type a generic parameter.
public abstract class DocumentLine { ... }
public class BillLine : DocumentLine { ... }
public class ReceiptLine : DocumentLine { ... }
public abstract class Document<TDocLine> where TDocLine : DocumentLine
{
public List<TDocLine> Lines { get; set; }
}
public class Bill : Document<BillLine> { ... }
public class Receipt : Document<ReceiptLine> { ... }
Deriving the line types from a common base has advantages. 1) you can reuse stuff common to both line types. 2) You can limit the actual types of TDocLine
. This safer as it disallows you to specify an inappropriate type and it allows you to access the common members declared in DocumentLine
from other methods in the Document<TDocLine>
class.
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.