I\'ve run into an interesting problem and am looking for some suggestions on how best to handle this...
I have an abstract class that contains a static method that accep
Elsewhere on this page, @Gusman proposes the nice solution distilled here:
abstract class AbstractBase { };
abstract class AbstractBase : AbstractBase
{
public static String AbstractStaticProp { get; set; }
};
class Derived1 : AbstractBase
{
public static new String AbstractStaticProp
{
get => AbstractBase.AbstractStaticProp;
set => AbstractBase.AbstractStaticProp = value;
}
};
class Derived2 : AbstractBase
{
public static new String AbstractStaticProp
{
get => AbstractBase.AbstractStaticProp;
set => AbstractBase.AbstractStaticProp = value;
}
};
Moving the static property from a non-generic to generic class means there is no longer necessarily a single global instance. There will be a unique AbstractStaticProp
for each distinct type T
, so the idea is that specifying the type of the derived class(es) themselves for T
guarantees each of them generates a unique static for themselves. There are a few hazards to note with this, however.
AbstractBaseClass
to be generic, then you've only moved the problem elsewhere (albeit more clearly distilled), because you still have to figure out how to statically call from AbstractBase
to AbstractBase
.TSelf
" of a derived class, in reality T
is unconstrained and essentially arbitrary. This opportunizes two new classes of bug: if base class specification Y : AbstractBase<...>
mistakenly references a different AbstractBase
‑derived class X
, the values of the "abstract static property" for X
and Y
will be incorrectly conflated -- and/or -- any usage call-site AbstractBase.AbstractStaticProp
with a mistaken type argument (such as DateTime
) will spontaneously--and silently--demand a fresh new "instance" of the static property.The last bullet point can be somewhat mitigated by adding a constraint on the generic base:
/// v---- constraint added
abstract class AbstractBase where TSelf : AbstractBase
{
public static String AbstractStaticProp { get; set; }
};
This eliminates the possibility of class Derived2 : AbstractBase
, but not the error class Derived2 : AbstractBase
. This is due to a recurring conundrum that foils all attempts at constraining a generic type to some exact branch of the type-inheritance hierarchy:
The "
TSelf
problem"
Generic constraints are always at the mercy of the type arguments that are supplied, which seems to entail that it's impossible to construct a generic constraint that guarantees that some particularTArg
within its scope refers to a type that is derived from itself, that is, the immediate type being defined.
The error in this case is an example of this; while the constraint on AbstractBase
rules out incompatible disjoint types, it can't rule out the unintended usage Derived2 : AbstractBase
. As far as AbstractBase
is concerned, the supplied type argument Derived1
satisfies its constraint just fine, regardless of which of its subtypes is deriving itself (im-)properly. I've tried everything, for years, to solve TSelf
; if anyone knows a trick I've missed, please let me know!
Anyway, there are still a couple other points to mention. For example, unless you can immediately spot the problem in the following code, you'll have to agree that it's a bit dangerous:
public static new String AbstractStaticProp
{
get => AbstractBase.AbstractStaticProp;
set => AbstractBase.AbstractStaticProp = value;
}
Ideally, you want to get the compiler to do what it's meant to, namely, understand that all AbstractStaticProp
property instances are related and thus somehow enforce their unification. Since that's not possible for static methods, the only remaining option is to eliminate the extra versions, effectively reducing the problem to the unification of just one, a vacuous operation, obviously.
It turns out that the original code is being too elaborate; the generic-base class approach wants to collapse on the simpler solution all by itself without having to explicitly request it, such as those new
-marked properties seem to be doing with the qualification in AbstractBase
".
You can already refer to each respective independent copy of the static property by qualifying with the derived class name instead (in fact, @Gusman's test harness shows this), so the end result is that the property declarations in the derived class aren't necessary at all. Without further ado, here is the complete simplified version:
abstract class AbstractBase { };
abstract class AbstractBase : AbstractBase
where TSelf : AbstractBase
{
public static String AbstractStaticProp { get; set; }
};
class Derived1 : AbstractBase { };
class Derived2 : AbstractBase { };
This works identically to the code at the top. The test harness gives the same results as before.
static void Test()
{
Derived1.AbstractStaticProp = "I am Derived1";
Derived2.AbstractStaticProp = "I am Derived2";
Debug.Print(Derived1.AbstractStaticProp); // --> I am Derived1
Debug.Print(Derived2.AbstractStaticProp); // --> I am Derived2
}