4

我有一个类(Foo),它延迟加载一个名为(Bar)的属性。防止未初始化的支持字段被错误使用(由于智能感知或缺乏经验的工作人员)的首选方法是什么?

我可以想到3个选项:

  class Foo {
    // option 1 - Easy to use this.bar by mistake. 
    string bar;
    string Bar {
        get {
            // logic to lazy load bar  
            return bar; 
        }
    }

    // option 2 - Harder to use this._bar by mistake. It is more obscure.
    string _bar2;
    string Bar2 {
        get {
            // logic to lazy load bar2  
            return _bar2;
        }
    }

    //option 3 - Very hard to use the backing field by mistake. 
    class BackingFields {
        public string bar; 
    }

    BackingFields fields = new BackingFields();

    string Bar3 {
        get {
            // logic to lazy load bar  
            return fields.bar;
        }
    }

}

请记住,我希望人们使用支持字段栏的唯一地方是属性的 setter 和 getter。在课堂上的其他任何地方,他们都应该始终使用 this.Bar

更新

我目前正在使用以下延迟实现(不是针对所有具有支持字段的属性,而是针对需要延迟加载、同步和通知的选定属性)。它也可以扩展到支持期货(稍后在单独的线程中强制评估)

注意我的实现在读取时锁定,因为它支持外部集。

另外,我想提一下,我认为这是一个可以在Ruby 中克服的语言限制。

您可以通过这种方式实现惰性

x = lazy do
    puts "<<< Evaluating lazy value >>>"
    "lazy value"
end

puts x
# <<< Evaluating lazy value >>>
# lazy value
4

14 回答 14

5

怎么样使用ObsoleteAttribute#pragma- 那就很难错过它!

    void Test1()
    {
        _prop = ""; // warning given
    }
    public string Prop
    {
#pragma warning disable 0618
        get { return _prop; }
        set { _prop = value; }
#pragma warning restore 0618
    }
    [Obsolete("This is the backing field for lazy data; do not use!!")]
    private string _prop;
    void Test2()
    {
        _prop = ""; // warning given
    }
于 2009-02-18T10:59:45.103 回答
4

选项 5

Lazy<T>

在几种情况下都可以很好地工作,但只要开发人员不是白痴,选项 1 对于大多数项目来说应该真的很好。

如果字段是私有的,则将 [EditorBrowsable(EditorBrowsableState.Never)] 添加到该字段将无济于事,因为此逻辑仅适用于从元数据而不是当前代码(当前项目以及通过项目引用而不是 dll 完成的任何操作)生成的智能感知。

注意:Lazy<T>不是线程安全的(这很好,如果您不需要,则没有必要锁定)如果您需要线程安全,请使用Joe Duffy 的线程安全之一或并行扩展 CTP

于 2009-02-18T10:58:41.907 回答
3

我通常选择选项 2,因为以后更容易发现错误,尽管选项 1 会通过代码审查。选项 3 似乎令人费解,虽然它可能有效,但在尝试重构/修复错误/等时重新审视 6 个月的代码并不是很好的代码。

于 2009-02-18T10:48:30.497 回答
2

选项1,加上一些教育。

基本原理:软件的阅读频率高于编写频率,因此针对常见情况进行优化并保持其可读性。

于 2009-02-18T10:43:18.120 回答
2

代码审查会发现误用,所以请选择最易读的。我不喜欢在代码中绕过糟糕的程序员,因为 1)他们不工作,2)他们让聪明的程序员更难完成他们的工作,以及 3)它解决了问题的症状而不是原因。

于 2009-02-18T10:59:41.560 回答
1

我通常只选择选项 1。因为它是一个私有字段,我认为这不是一个真正的问题,并且在选项 3 中使用类似包装类的东西只会使代码难以阅读和理解。

于 2009-02-18T10:42:21.493 回答
1

我会在类的顶部放一个大的注释块,看起来像这样:

/************************************************************
* Note: When updating this class, please take care of using *
*       only the accessors to access member data because of *
*       ... (state the reasons / your name, so they can ask *
*       questions)                                          *
*************************************************************/

通常,只需这样的注释就足够了,但如果项目中的所有类都相同,您可能更愿意将其放在一个简单的文档中,该文档可以提供给从事该项目的程序员,并且每次看到代码时这不符合,您将它们指向文档。

于 2009-02-27T21:01:13.913 回答
0
// option 4
class Foo
{
    public int PublicProperty { get; set; }
    public int PrivateSetter { get; private set; }
}

C# 3.0 的特性,编译器会生成匿名的私有支持字段,这些字段不能被错误访问,除非你使用反射......

编辑:延迟实例化

你可以有这样的懒惰:

// I changed this with respect to ShuggyCoUk's answer (Kudos!)
class LazyEval<T>
{
  T value;
  Func<T> eval;
  public LazyEval(Func<T> eval) { this.eval = eval; }
  public T Eval()
  {
      if (eval == null)
          return value;
      value = eval();
      eval = null;
      return value;
  }
  public static implicit operator T(LazyEval<T> lazy) // maybe explicit
  {
    return lazy.Eval();
  }
  public static implicit operator LazyEval<T>(Func<T> eval) 
  {
    return new LazyEval(eval);
  } 
}

那些隐式转换使语法整洁......

// option 5
class Foo
{
    public LazyEval<MyClass> LazyProperty { get; private set; }
    public Foo()
    {
        LazyProperty = () => new MyClass();
    }
}

闭包可用于承载范围:

// option 5
class Foo
{
    public int PublicProperty { get; private set; }
    public LazyEval<int> LazyProperty { get; private set; }
    public Foo()
    {
        LazyProperty = () => this.PublicProperty;
    }
    public void DoStuff()
    {
        var lazy = LazyProperty; // type is inferred as LazyEval`1, no eval
        PublicProperty = 7;
        int i = lazy; // same as lazy.Eval()
        Console.WriteLine(i); // WriteLine(7)
    }
}
于 2009-02-18T10:47:07.910 回答
0

自动属性:

public int PropertyName { get; set; }

将阻止访问支持字段。但是如果你想把代码放在那里(例如第一次访问时延迟加载),这显然无济于事。

最简单的路线可能是一个辅助类型,它执行延迟加载,并具有该类型的私有字段,公共属性调用辅助类型的正确属性/方法。

例如

public class Foo {
  private class LazyLoader {
    private someType theValue;
    public someType Value {
      get {
        // Do lazy load
        return theValue;
      }
    }
  }

  private LazyLoader theValue;
  public someType {
    get { return theValue.Value; }
  }
}

这样做的好处是支持字段比属性更难使用。

(另一种解决问题的额外间接级别的情况。)

于 2009-02-18T10:54:02.760 回答
0

目前,我处于类似的情况。我有一个只能由其属性访问器使用的字段。
我不能使用自动属性,因为我需要在设置属性时执行额外的逻辑。(该属性也不是延迟加载的)。

如果 C# 的下一个版本允许这样的事情,那不是很好吗:

public class MyClass
{
    public int MyProperty
    {
        int _backingField;

        get
        {
            return _backingField;
        }
        set
        {
            if( _backingField != value )
            {
                _backingField = value;
                // Additional logic
                ...
            }
        }
    }
}

使用这种构造,_backingField变量的范围仅限于属性。我希望在下一个 C# 版本中看到类似的语言结构:)

但是,恐怕这个功能永远不会实现: http ://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=381625

于 2009-04-17T13:34:13.797 回答
0

这可能过于简单,但为什么不将所有惰性抽象为基类

 public class LazyFoo{
      private string bar;
      public string Bar{
         get{
             // lazy load and return
         }
         set {
             // set
         }
      }
    }

    public class Foo : LazyFoo{
      // only access the public properties here
    }

我可以看到它是不必要的抽象的论点,但这是我能看到的消除对支持字段的所有访问的最简单方法。

于 2009-04-17T13:50:41.947 回答
0

这似乎是在试图设计出一开始可能不会发生的错误,而且基本上是在担心错误的事情。

我会选择选项 1 + 评论:

///<summary>use Bar property instead</summary> 
string bar;

///<summary>Lazy gets the value of Bar and stores it in bar</summary>
string Bar {
    get {
        // logic to lazy load bar  
        return bar; 
    }
}

如果您确实找到了一个继续使用支持变量的开发人员,那么我会担心他们的技术能力。

无论如何设计使您的代码更易于维护,但尽量保持简单 - 您在这里为自己制定的任何规则都将比它的价值更麻烦。

如果您仍然非常担心它,请创建一个 FxCop(或您正在使用的任何东西)规则来检查此类事情。

于 2009-04-17T13:56:05.007 回答
0

选项 6:

如果你使用它,它确实非常愚蠢。

string doNotUseThisBackingField_bar6;
string Bar6 {
   get {
      // logic to lazy load
      return doNotUseThisBackingField_bar6;
   }
}
于 2022-02-22T20:09:23.847 回答
-1

选项 4(新解决方案)

看看问题是否真的是关于如何防止人们使用未初始化的变量,然后使用 KNOWN INVALID 值对其进行初始化。

我会说这样的话:

string str = "SOMETHING_WRONG_HERE";

曾经使用“str”的人会收到某种警告。

否则选项 3 如果阻止用户使用“str”比可读性等更重要。

于 2009-02-18T10:49:30.743 回答