收录日期:2019/12/16 12:52:48 时间:2010-10-28 14:10:33 标签:c#,generics,inheritance,polymorphism

I have the following class

public class AccountingBase<TItemType> where TItemType : AccountingItemBase

And in my AccountingItemBase i have the following property:

public virtual AccountingBase<AccountingItemBase> Parent { get; set; }

in my AccountingBase, I am trying to do the following

item.Parent = this;

Logically this should work, as TItemType inherits from AccountingItemBase, but instead i get the following error:

> Error 1 Cannot implicitly convert type
> 'TGS.MySQL.DataBaseObjects.AccountingBase<TItemType>'
> to
> 'TGS.MySQL.DataBaseObjects.AccountingBase<TGS.MySQL.DataBaseObjects.AccountingItemBase>'

How can i set the child properties parent property to itself (inside the parent class)

No, your intuition is incorrect. It shouldn't work, because generic classes aren't variant in .NET.

Just because TItemType inherits from AccountingItemBase doesn't mean that AccountingBase<TItemType> inherits from AccountingBase<AccountingItemBase>. Suppose AccountingBase<TItemType> had a field of type TItemType. Then if your intuition were correct, you could write:

AccountingBase<SomeSubtype> x = new AccountingBase<SomeSubtype>();
AccountingBase<AccountingItemBase> y = x;
y.SomeField = new OtherSubtype();

That would clearly break type safety, because when looked at as an AccountingBase<SomeSubtype>, the field is meant to be of type SomeSubtype, but you've put a value of type OtherSubtype in there!

Basically, generic variance is a complex topic.

I suggest you read Eric Lippert's long and detailed blog post series for more information. Or I have a video from NDC 2010 which you may find useful. Basically in .NET 4 there's some generic variance, but it's limited.

Now, as to what you can do in your situation:

  • You could create a nongeneric base class which AccountingBase inherits from. That's probably the best idea. Then make the type of the Parent property that nongeneric type.
  • You could make AccountingBase generic in both itself and its parent... but that ends up causing recursion issues, effectively...

In addition to Jon's options, you could:

  • Create an interface IAccountingBase that provides only the limited access required by AccountingItemBase to do its work (similar to a non-generic base class, but further abstracted.)

  • Restructure your code so that AccountingItemBase doesn't need a Parent reference to do its work. My experience has been that circular dependencies (Owner knows about Items which know about Owner) are symptomatic of a design where the Child instances are taking on too many responsibilities. You can sometimes get around this by moving the functionality to the Parent or by moving it to higher-level classes that perform complex operations on multiple Items in the context of a single Parent, eliminating the need for each Item to have a Parent reference. For instance, if your Items expose functions related to Account reconciliation, you might have an AccountReconciler class rather than putting the functions on the Items.