如何拥有C#只读功能但不限于构造函数?

发布于 2024-12-15 10:28:28 字数 190 浏览 3 评论 0原文

C#“readonly”关键字是一个修饰符,当字段声明包含它时,对声明引入的字段的赋值只能作为声明的一部分或同一类的构造函数中发生。

现在假设我确实想要这个“赋值一次”约束,但我宁愿允许在构造函数之外完成赋值,可能是惰性/延迟评估/初始化。

我怎么能这么做呢?是否可以以一种很好的方式做到这一点,例如是否可以编写一些属性来描述这一点?

The C# "readonly" keyword is a modifier that when a field declaration includes it, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.

Now suppose I do want this "assign value once" constraint, but I would rather allow the assignment be done outside of constructors, a lazy/late evaluation/initialization maybe.

How could I do that? and is it possible to do it in a nice way, for example, is it possible to write some attribute to describe this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

夜血缘 2024-12-22 10:28:28

如果我正确理解你的问题,听起来你只想设置一个字段的值一次(第一次),并且不允许此后设置它。如果是这样,那么之前所有关于使用 Lazy(和相关)的帖子可能会有用。但如果您不想使用这些建议,也许您可​​以这样做:

public class SetOnce<T> 
{
    private T mySetOnceField;
    private bool isSet;

    // used to determine if the value for 
    // this SetOnce object has already been set.
    public bool IsSet
    {
      get { return isSet; }
    }
    // return true if this is the initial set, 
    // return false if this is after the initial set.
    // alternatively, you could make it be a void method
    // which would throw an exception upon any invocation after the first.
    public bool SetValue(T value)
    {
       // or you can make thread-safe with a lock..
       if (IsSet)
       {
          return false; // or throw exception.
       }
       else 
       {
          mySetOnceField = value;
          return isSet = true;
       }
    }

    public T GetValue()
    {
      // returns default value of T if not set. 
      // Or, check if not IsSet, throw exception.
      return mySetOnceField;         
    }
} // end SetOnce

public class MyClass 
{
  private SetOnce<int> myReadonlyField = new SetOnce<int>();
  public void DoSomething(int number)
  {
     // say this is where u want to FIRST set ur 'field'...
     // u could check if it's been set before by it's return value (or catching the exception).
     if (myReadOnlyField.SetValue(number))
     {
         // we just now initialized it for the first time...
         // u could use the value: int myNumber = myReadOnlyField.GetValue();
     }
     else
     {
       // field has already been set before...
     }

  } // end DoSomething

} // end MyClass

If I understand your question correctly, it sounds like you just want to set a field's value once (the first time), and not allow it to be set after that. If that is so, then all the previous posts about using Lazy (and related) may be useful. But if you don't want to use those suggestions, perhaps you can do something like this:

public class SetOnce<T> 
{
    private T mySetOnceField;
    private bool isSet;

    // used to determine if the value for 
    // this SetOnce object has already been set.
    public bool IsSet
    {
      get { return isSet; }
    }
    // return true if this is the initial set, 
    // return false if this is after the initial set.
    // alternatively, you could make it be a void method
    // which would throw an exception upon any invocation after the first.
    public bool SetValue(T value)
    {
       // or you can make thread-safe with a lock..
       if (IsSet)
       {
          return false; // or throw exception.
       }
       else 
       {
          mySetOnceField = value;
          return isSet = true;
       }
    }

    public T GetValue()
    {
      // returns default value of T if not set. 
      // Or, check if not IsSet, throw exception.
      return mySetOnceField;         
    }
} // end SetOnce

public class MyClass 
{
  private SetOnce<int> myReadonlyField = new SetOnce<int>();
  public void DoSomething(int number)
  {
     // say this is where u want to FIRST set ur 'field'...
     // u could check if it's been set before by it's return value (or catching the exception).
     if (myReadOnlyField.SetValue(number))
     {
         // we just now initialized it for the first time...
         // u could use the value: int myNumber = myReadOnlyField.GetValue();
     }
     else
     {
       // field has already been set before...
     }

  } // end DoSomething

} // end MyClass
眼泪都笑了 2024-12-22 10:28:28

现在假设我确实想要这个“赋值一次”约束,但我宁愿允许在构造函数之外完成赋值

请注意,延迟初始化很复杂,因此对于所有这些答案,如果您有多个线程,您应该小心尝试访问您的对象。

如果您想在类内部执行此操作

您可以使用 C# 4.0 内置的延迟初始化功能:

或者对于旧版本的 C#,只需提供 get 方法,并检查您是否已使用支持字段进行初始化:

public string SomeValue
{
    get
    {
        // Note: Not thread safe...
        if(someValue == null)
        {
            someValue = InitializeSomeValue(); // Todo: Implement
        }

        return someValue;
    }
}

如果您想在类之外执行此操作

您想要 Popsicle 不变性:

基本上:

  • 您制作了整个可写,但添加一个 Freeze 方法。
  • 调用此 freeze 方法后,如果用户尝试调用类上的 setter 或 mutator 方法,您将引发 ModifyFrozenObjectException
  • 您可能需要一种方法让外部类确定您的类是否IsFrozen

顺便说一句,这些名字是我刚刚起的。诚然,我的选择很差,但目前还没有普遍遵循的惯例。

目前,我建议您创建一个 IReezable 接口以及可能的相关异常,这样您就不必依赖于 WPF 实现。像这样的东西:

public interface IFreezable
{
    void Freeze();
    bool IsFrozen { get; }
}

Now suppose I do want this "assign value once" constraint, but I would rather allow the assignment be done outside of constructors

Note that lazy initialization is complicated, so for all of these answers you should be careful if you have multiple threads trying to access your object.

If you want to do this inside the class

You can use the C# 4.0 built-in lazy initialization features:

Or for older versions of C#, just supply a get method, and check if you're already initialized by using a backing field:

public string SomeValue
{
    get
    {
        // Note: Not thread safe...
        if(someValue == null)
        {
            someValue = InitializeSomeValue(); // Todo: Implement
        }

        return someValue;
    }
}

If you want to do this outside the class

You want Popsicle Immutability:

Basically:

  • You make the whole class writable, but add a Freeze method.
  • Once this freeze method is called, if users try to call setters or mutator methods on your class, you throw a ModifyFrozenObjectException.
  • You probably want a way for external classes to determine if your class IsFrozen.

BTW, I made up these names just now. My selections are admittedly poor, but there is no generically followed convention for this yet.

For now I'd recommend you create an IFreezable interface, and possibly related exceptions, so you don't have to depend on the WPF implementation. Something like:

public interface IFreezable
{
    void Freeze();
    bool IsFrozen { get; }
}
差↓一点笑了 2024-12-22 10:28:28

您可以使用 Lazyclass:

private readonly Lazy<Foo> _foo = new Lazy<Foo>(GetFoo);

public Foo Foo
{
    get { return _foo.Value; }
}

private static Foo GetFoo()
{
    // somehow create a Foo...
}

GetFoo 仅在您第一次调用 Foo 属性时才会被调用。

You can use the Lazy<T> class:

private readonly Lazy<Foo> _foo = new Lazy<Foo>(GetFoo);

public Foo Foo
{
    get { return _foo.Value; }
}

private static Foo GetFoo()
{
    // somehow create a Foo...
}

GetFoo will only be called the first time you call the Foo property.

清风不识月 2024-12-22 10:28:28

这在埃菲尔铁塔中被称为“一次”功能。这是 C# 中的一个重大疏忽。新的 Lazy 类型是一个很差的替代品,因为它不能与其非惰性版本互换,而是要求您通过其 Value 属性访问包含的值。因此,我很少使用它。噪声是 C# 代码的最大问题之一。理想情况下,人们想要这样的东西......

public once Type PropertyName { get { /* generate and return value */ } }

而不是当前的最佳实践......

Type _PropertyName; //where type is a class or nullable structure
public Type PropertyName
{
    get
    {
        if (_PropertyName == null)
            _PropertyName = /* generate and return value */ 
        return _PropertyName
    }
}

This is know as the "once" feature in Eiffel. It is a major oversight in C#. The new Lazy type is a poor substitute since it is not interchangeable with its non-lazy version but instead requires you to access the contained value through its Value property. Consequently, I rarely use it. Noise is one of the biggest problems with C# code. Ideally, one wants something like this...

public once Type PropertyName { get { /* generate and return value */ } }

as oppose to the current best practice...

Type _PropertyName; //where type is a class or nullable structure
public Type PropertyName
{
    get
    {
        if (_PropertyName == null)
            _PropertyName = /* generate and return value */ 
        return _PropertyName
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文