【问题标题】:What is the best way to define a static property which is defined once per sub-class?定义每个子类定义一次的静态属性的最佳方法是什么?
【发布时间】:2013-07-22 05:25:02
【问题描述】:

我编写了以下控制台应用程序来测试静态属性:

using System;

namespace StaticPropertyTest
{
    public abstract class BaseClass
    {
        public static int MyProperty { get; set; }
    }

    public class DerivedAlpha : BaseClass
    {
    }

    public class DerivedBeta : BaseClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            DerivedBeta.MyProperty = 7;
            Console.WriteLine(DerivedAlpha.MyProperty); // outputs 7
        }
    }
}

正如这个控制台应用所展示的,MyProperty 属性对于 BaseClass 的所有实例只存在一次。是否有一种模式可以让我定义一个静态属性,该属性将为每个子类类型分配存储空间?

鉴于上述示例,我希望DerivedAlpha 的所有实例共享相同的静态属性,并且DerivedBeta 的所有实例共享静态属性的另一个实例。

我为什么要这样做?

我正在懒惰地初始化具有某些属性的类属性名称集合(通过反射)。每个派生类实例的属性名称都是相同的,因此将其存储在每个类实例中似乎很浪费。我不能在基类中将其设为静态,因为不同的子类会有不同的属性。

我不想在每个派生类中复制填充集合(通过反射)的代码。我知道一种可能的解决方案是在基类中定义填充集合的方法,并从每个派生类中调用它,但这不是最优雅的解决方案。

更新 - 我在做什么的示例

应乔恩的要求,这是我正在尝试做的一个示例。基本上,我可以选择使用[SalesRelationship(SalesRelationshipRule.DoNotInclude)] 属性来装饰我的类中的属性(还有其他属性,这只是一个简化的示例)。

public class BaseEntity
{
    // I want this property to be static but exist once per derived class.
    public List<string> PropertiesWithDoNotInclude { get; set; }

    public BaseEntity()
    {
        // Code here will populate PropertiesWithDoNotInclude with
        // all properties in class marked with
        // SalesRelationshipRule.DoNotInclude.
        //
        // I want this code to populate this property to run once per
        // derived class type, and be stored statically but per class type.
    }
}

public class FooEntity : BaseEntity
{
   [SalesRelationship(SalesRelationshipRule.DoNotInclude)]
   public int? Property_A { get; set; }

   public int? Property_B { get; set; }

   [SalesRelationship(SalesRelationshipRule.DoNotInclude)]
   public int? Property_C { get; set; }
}

public class BarEntity : BaseEntity
{
   public int? Property_D { get; set; }

   [SalesRelationship(SalesRelationshipRule.DoNotInclude)]
   public int? Property_E { get; set; }

   public int? Property_F { get; set; }
}

期望的最终结果

访问FooEntity.PropertiesWithDoNotInclude 会返回List&lt;string&gt;

{
  "Property_A",
  "Property_C"
}

访问BarEntity.PropertiesWithDoNotInclude 会返回一个List&lt;string&gt;

{
  "Property_E"
}

【问题讨论】:

    标签: c# .net reflection static


    【解决方案1】:

    两种可能的方法:

    • 使用属性;用属性装饰每个子类,例如

      [MyProperty(5)]
      public class DerivedAlpha
      {
      }
      
      [MyProperty(10)]
      public class DerivedBeta
      {
      }
      

      当然,这只有在它们实际上是常量时才有效。

    • 使用字典:

      var properties = new Dictionary<Type, int>
      {
          { typeof(DerivedAlpha), 5) },
          { typeof(DerivedBeta), 10) },
      };
      

    编辑:现在我们有了更多的上下文,Ben 的答案非常好,使用泛型在 C# 中的工作方式。它类似于字典示例,但内置了惰性、线程安全和简单的全局访问。

    【讨论】:

    • 我已经扩展了我的答案。
    • 在您的第二个示例中,您的意思是 DerivedBeta 用于第二个项目吗? :)
    【解决方案2】:

    Jon 和往常一样有一个很好的解决方案,虽然我看不出这里有什么好的属性,因为它们必须显式添加到每个子类型中,而且它们的行为不像属性。

    Dictionary 方法绝对可行。这是另一种方法,它明确声明BaseEntity 的每个子类将有一个变量:

    class FilteredProperties<T> where T : BaseEntity
    {
         static public List<string> Values { get; private set; }
         // or static public readonly List<string> Values = new List<string>();
         static FilteredProperties()
         {
             // logic to populate the list goes here
         }
    }
    

    这样做的缺点是很难与GetType() 调用配对,例如您可能在BaseEntity 的方法中使用。 Dictionary 或实现惰性填充的包装器更适合这种用法。

    【讨论】:

    • OP 指定“每个派生类实例的属性名称都是相同的” - 这表明它实际上是每个类的常量,这意味着它可以是一个属性,然后通过反射获取。不,它的行为不完全像一个属性 - 但它允许您为每个类指定一个值,然后获取该值......现在我们在问题中获得了更多信息,更清楚的是 OP 试图做什么,从Typesomething 的字典可能是要走的路。
    • 您的解决方案是否打算使用单例(按类型参数)方法?如果是这样,我喜欢它 - 但你可以稍微扩展一下。
    • @Jon:哎呀,我错过了static 关键字。
    • 正确 - 现在这很有意义。将修改我的答案以将此称为一个好的解决方案。可能值得明确说明泛型在 C# 中的工作方式,最终每组类型参数都有一个单独的类型,这就是它工作的原因。
    【解决方案3】:

    我最近刚需要同样的东西并遇到了这个问题。我认为 Jon 和 Fried 使用 Dictionary 的想法是正确的,但并没有完全达到我想要的效果,所以我想我会展示我自己的完整且非常易于扩展的实现。

    public class TypeStaticProperty<T>
    {
        T _defaultValue;
        Dictionary<Type, T> _values = new Dictionary<Type, T>();
    
        public TypeStaticProperty(T defalutValue = default)
        {
            _defaultValue = defalutValue;
        }
        
        public T Get(object caller)
        {
            lock (_values)
            {
                if (_values.TryGetValue(caller?.GetType(), out T val))
                    return val;
                else
                    return _defaultValue;
            }
        }
    
        public void Set(object caller, T val)
        {
            lock (_values)
                _values[caller?.GetType()] = val;
        }
    }
    

    并演示:

    class TestBaseClass
    {
        static TypeStaticProperty<int> _property = new TypeStaticProperty<int>();
        public int Property
        {
            get => _property.Get(this);
            set => _property.Set(this, value);
        }
    }
    
    class TestClass1 : TestBaseClass
    {
    }
    
    class TestClass2 : TestBaseClass
    {
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            TestClass1 test1a = new TestClass1();
            TestClass1 test1b = new TestClass1();
    
            test1a.Property = 1;
            test1b.Property = 2;
    
            TestClass2 test2a = new TestClass2();
            TestClass2 test2b = new TestClass2();
    
            test2a.Property = 3;
            test2b.Property = 4;
    
            Console.WriteLine($"test1a.Property = {test1a.Property}");
            Console.WriteLine($"test1b.Property = {test1b.Property}");
            Console.WriteLine($"test2a.Property = {test2a.Property}");
            Console.WriteLine($"test2b.Property = {test2b.Property}");
        }
    }
    

    输出:

    test1a.Property = 2
    test1b.Property = 2
    test2a.Property = 4
    test2b.Property = 4
    

    因此,尽管您仍然需要一个类实例来访问和设置属性,但该精确类型的所有实例的值将始终相同。 (这也包括泛型;Foo&lt;int&gt; 将被视为与Foo&lt;string&gt; 不同的类型)。与 Fried 的示例相比,这具有巨大的优势,因为您无需在编译时知道在访问或设置时要查找的“静态”值的精确类型。

    PS - 为了全面披露,这在很大程度上受到了 WPF 源代码的启发,该源代码对 DependencyProperty 和各种其他旨在提高性能和减少内存占用的内部花哨的模式使用了非常相似的模式。

    【讨论】:

      猜你喜欢
      • 2022-08-23
      • 2015-12-11
      • 1970-01-01
      • 2019-04-11
      • 1970-01-01
      • 1970-01-01
      • 2012-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多