I'm pretty sure you're out of luck as far as using the compiler and generics to save you some runtime checks. You can't override something that doesn't already exist, and you can't have different return types to the same methods.
I can't say I completely understand your motivation, but it has technical merit.
My first attempt was using the base class having a Non-Virtual public interface, and then having another protected virtual method CheckCreatedType
that would allow anything in the chain to inspect the type before the base class Create was called.
public class A
{
public IFieldSimpleItem Create()
{
IFieldSimpleItem created = InternalCreate();
CheckCreatedType(created);
return created;
}
protected virtual IFieldSimpleItem InternalCreate()
{
return new SimpleImpl();
}
protected virtual void CheckCreatedType(IFieldSimpleItem item)
{
// base class doesn't care. compiler guarantees IFieldSimpleItem
}
}
public class B : A
{
protected override IFieldSimpleItem InternalCreate()
{
// does not call base class.
return new NormalImpl();
}
protected override void CheckCreatedType(IFieldSimpleItem item)
{
base.CheckCreatedType(item);
if (!(item is IFieldNormalItem))
throw new Exception("I need a normal item.");
}
}
The following sticks in runtime checking at the base class. The unresolvable issue is you still have to rely on the base class method being called. A misbehaving subclass can break all checks by not calling base.CheckCreatedType(item)
.
The alternatives are you hardcode all the checks for all subclasses inside the base class (bad), or otherwise externalize the checking.
Attempt 2: (Sub)Classes register the checks they need.
public class A
{
public IFieldSimpleItem Create()
{
IFieldSimpleItem created = InternalCreate();
CheckCreatedType(created);
return created;
}
protected virtual IFieldSimpleItem InternalCreate()
{
return new SimpleImpl();
}
private void CheckCreatedType(IFieldSimpleItem item)
{
Type inspect = this.GetType();
bool keepgoing = true;
while (keepgoing)
{
string name = inspect.FullName;
if (CheckDelegateMethods.ContainsKey(name))
{
var checkDelegate = CheckDelegateMethods[name];
if (!checkDelegate(item))
throw new Exception("failed check");
}
if (inspect == typeof(A))
{
keepgoing = false;
}
else
{
inspect = inspect.BaseType;
}
}
}
private static Dictionary<string,Func<IFieldSimpleItem,bool>> CheckDelegateMethods = new Dictionary<string,Func<IFieldSimpleItem,bool>>();
protected static void RegisterCheckOnType(string name, Func<IFieldSimpleItem,bool> checkMethod )
{
CheckDelegateMethods.Add(name, checkMethod);
}
}
public class B : A
{
static B()
{
RegisterCheckOnType(typeof(B).FullName, o => o is IFieldNormalItem);
}
protected override IFieldSimpleItem InternalCreate()
{
// does not call base class.
return new NormalImpl();
}
}
The check is done by the subclass registering a delegate to invoke in base class, but without the base class knowing all the rules upfront. Notice too that it's still the Non-Virtual public interface which allows the base class to check the results before returning them.
I'm assuming that it's a developer error that you're trying to catch. If it's applicable, you can adorn the runtime check method with System.Diagnostics.Conditional("DEBUG")]
, allowing the Release version to skip the checks.
My knowledge of generics isn't perfect, so maybe this is unnecessary. However the checks here don't have to be for type alone: this could be adapted for other uses. e.g. the delegate passed in Register..
doesn't have to just check the reference is a specific type'
* Note that it's probably not good to create the dictionary on the type name as written above; this working is a little simplistic in order to illustrate the mechanism used.