如何实现虚拟静态属性?

10
据我所知,C#不支持虚拟静态属性。如何在C#中实现这样的行为?
我想要实现这样一个目标:所有派生类都必须重写基类的静态属性。当获取一个派生类型时,我想要访问名为Identifier的静态属性。
Type t = typeof(DerivedClass);
var identifier= (String) t.GetProperty("Identifier", BindingFlags.Static).GetValue(null, null);

静态成员不能被覆盖(或者是“重写”?)请原谅我的糟糕英语 =( - Federico Berasategui
正如你所说,C#不支持这个功能,所以你无法实现它。 - Servy
1
如果它真的是“静态”的,为什么还要使用“虚拟”的? - Parimal Raj
1
不,我不需要对象,只需要类型。如果有一个对象,静态将不再需要。 - Razer
1
可能是为什么C#不能有抽象静态方法?的重复问题。 - Ken Kin
显示剩余3条评论
4个回答

6
C# 10 在接口中引入了静态抽象方法并简化了解决方案。
有了这个新的语言特性,您可以将派生类标记为实现您的标识符属性作为静态属性:
public interface IClass
{
    static abstract string Identifier { get; }
}

public class DerivedClass1 : IClass
{
    public static string Identifier => "DerivedClass1";
}

public class DerivedClass2 : IClass
{
    public static string Identifier => "DerivedClass2";
}

为了使用它,您需要.NET 6,并在应用程序的csproj文件中将LangVersion设置为预览。请参阅微软文档:https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/static-abstract-interface-methods

1
也可以查看 https://dev59.com/c3VD5IYBdhLWcg3wXaYd#69666855。 - StayOnTarget

6
对于那些通过谷歌搜索到这篇文章并考虑解决方案的人,可以考虑使用“抽象工厂模式”(abstract factory pattern)而非本文提供的解决方案。
--
五年后仍然没有被采纳的答案,让我再试一次。我曾经想过使用“奇异递归模板模式”(Curiously Recurring Template Pattern)作为解决方案,但既然您要开放 BaseClass 以便继承,这并不是一个好主意。您可能需要查看 Mr. Lippert 的博客文章以更好地理解其中原因。
  • Solution 1: You don't register, I don't recognize ..

    public abstract class BaseClass {
        protected static void Register<U>(String identifier) where U : BaseClass {
            m_identities.Add(typeof(U).GetHashCode(), identifier);
        }
    
        public static String GetIdentifier<U>() where U : BaseClass {
            var t = typeof(U);
            var identifier = default(String);
            RuntimeHelpers.RunClassConstructor(t.TypeHandle);
            m_identities.TryGetValue(t.GetHashCode(), out identifier);
            return identifier;
        }
    
        static Dictionary<int, String> m_identities = new Dictionary<int, String> { };
    }
    
    public class DerivedClassA:BaseClass {
        static DerivedClassA() {
            BaseClass.Register<DerivedClassA>("12dc2490-065d-449e-a199-6ba051c93622");
        }
    }
    
    public class DerivedClassB:BaseClass {
        static DerivedClassB() {
            BaseClass.Register<DerivedClassB>("9745e24a-c38b-417d-a44d-0717e10e3b96");
        }
    }
    

    test:

    Debug.Print("{0}", BaseClass.GetIdentifier<DerivedClassA>());
    Debug.Print("{0}", BaseClass.GetIdentifier<DerivedClassB>());
    

这是一种相对简单的模式,通过类型初始化器实现。 Register 方法仅公开给派生类使用;而且 GetIdentifierRegister 方法都限制只能使用派生自 BaseClass 的类型参数进行调用。虽然我们不强制要求派生类覆盖任何内容,但如果它没有注册自己,则 GetIdentifier 将无法识别它并返回 null

  • Solution 2: Before you show your identity, I buy you a default. Whoever you think you are, I believe -- as long as there are no ambiguity.

    public abstract class BaseClass {
        public abstract String Identifier {
            get;
        }
    
        public static Type GetDerivedClass(String identifier) {
            return m_aliases[identifier];
        }
    
        public static String GetIdentifier(Type t) {
            var value = default(String);
    
            if(t.IsSubclassOf(typeof(BaseClass))) {
                var key = t.GetHashCode();
    
                if(!m_identities.TryGetValue(key, out value)) {
                    value=""+key;
                    m_aliases.Add(value, t);
                    m_identities[key]=value;
                }
            }
    
            return value;
        }
    
        static void UpdateAlias(BaseClass x) {
            var t = x.GetType();
            var value = x.Identifier;
            m_aliases.Add(value, t);
            m_identities[t.GetHashCode()]=value;
        }
    
        protected BaseClass() {
            BaseClass.UpdateAlias(this);
        }
    
        static Dictionary<String, Type> m_aliases = new Dictionary<String, Type> { };
        static Dictionary<int, String> m_identities = new Dictionary<int, String> { };
    }
    

    public class DerivedClassA:BaseClass {
        public override String Identifier {
            get {
                return "just text";
            }
        }
    }
    
    public class DerivedClassB:BaseClass {
        public override String Identifier {
            get {
                return "just text";
            }
        }
    }
    

    and the test:

    public static void TestMethod() {
        var idBeforeInstantiation = BaseClass.GetIdentifier(typeof(DerivedClassA));
        var y = new DerivedClassA { };
        var idAfterInstantiation = BaseClass.GetIdentifier(typeof(DerivedClassA));
    
        Debug.Print("B's: {0}", BaseClass.GetIdentifier(typeof(DerivedClassB)));
        Debug.Print("A's after: {0}", idAfterInstantiation);
        Debug.Print("A's before: {0}", idBeforeInstantiation);
        Debug.Print("A's present: {0}", BaseClass.GetIdentifier(typeof(DerivedClassA)));
    
        var type1 = BaseClass.GetDerivedClass(idAfterInstantiation);
        var type2 = BaseClass.GetDerivedClass(idBeforeInstantiation);
    
        Debug.Print("{0}", type2==type1); // true
        Debug.Print("{0}", type2==typeof(DerivedClassA)); // true
        Debug.Print("{0}", type1==typeof(DerivedClassA)); // true
    
        var typeB=BaseClass.GetDerivedClass(BaseClass.GetIdentifier(typeof(DerivedClassB)));
    
        var x = new DerivedClassB { }; // confilct
    }
    

显然,这是一个更为复杂的解决方案。正如您所看到的idBeforeInstantiationidAfterInstantiation是不同的,但它们都是DerivedClassA的有效标识符。m_identities包含每个派生类的最后更新标识符,而m_aliases将包含所有派生类的标识符别名。由于目前(也许永远)语言中不存在虚拟和静态的组合特性,如果我们想强制执行override,则必须通过一些变通方式来实现。如果您选择solution2,则可能需要实现自己的UpdateAlias,以防止派生类为单个类型提供过多的各种别名,尽管它们全部都是有效的。测试中的最后一条语句被故意放置以演示标识符的冲突。

对于这两个解决方案,它们都经过了精心设计,考虑到不实例化派生类,但它们并不要求这样做。


4

简单来说,你做不到这件事,所以我谦虚地建议你放弃并尝试其他方法。

请参考这篇SO帖子中的答案。如果你能够实现这样的功能,你将会在继承方面遇到严重的问题。

我曾经尝试过这种方法,但后来恢复了理智,采用了常规的继承方法。我认为你也应该这样做。


0

另一种不需要注册类但需要额外工作的方法是创建一个静态类,该类保存每个派生类类型的“静态”数据,并从静态类返回常量/静态值。让我解释一下这种方法的具体细节。

拥有始终相同的静态属性对于类的每个成员都非常重要的一个原因是避免不必要的内存使用和重复。虽然此处演示的方法并不能完全避免这种情况,但它仍然可以绕过大部分“额外”的开销。下面的示例唯一无法满足的用例是,如果使用静态属性的原因是因为您不必拥有实例,因为您必须拥有实例才能访问数据。

如果您需要一个虚拟字段或属性,该字段或属性对于类的每个成员始终相同(静态),请使用返回“常量”或静态数据的非静态属性,如下所示:

public static class MyStaticData
{
    public static const string Class1String = "MyString1";
    public static const int Class1Int = 1;
    public static const string Class2String = "MyString2";
    public static const int Class2Int = 2;
    // etc...
}

public abstract class MyBaseClass
{
    public abstract string MyPseudoVirtualStringProperty { get; }
    public abstract int MyPseudoVirtualIntProperty { get; }
}

public class MyDerivedClass1 : My BaseClass
{
    public override string MyPseudoVirtualStringProperty { get { return MyStaticData.Class1String; } }
    public override int MyPseudoVirtualIntProperty { get { return MyStaticData.Class1Int } }
}

public class MyDerivedClass2 : My BaseClass
{
    public override string MyPseudoVirtualStringProperty { get { return MyStaticData.Class2String; } }
    public override int MyPseudoVirtualIntProperty { get { return MyStaticData.Class2Int } }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接