Can't define static abstract string property

后端 未结 5 1103
别跟我提以往
别跟我提以往 2021-02-19 10:03

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

5条回答
  •  挽巷
    挽巷 (楼主)
    2021-02-19 10:47

    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.

    • If for some reason it is not acceptable for 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.
    • Mainly, there is nothing to enforce or require that any/every given derived class actually does "implement" the (psudo-) "overridden" static property;
    • Related to this, since there is no compiler (polymorphic) unification going on here, correct signatures (method name, parameter arity, typing, etc.) for the "overridden" methods aren't enforced either.
    • Although the generic parameter is intended to be "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‑der­ived 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 particular TArg 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.​AbstractStaticProp".

    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
    }
    

提交回复
热议问题