How to have a C# readonly feature but not limited to constructor?
Asked Answered
H

4

5

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?

Harrovian answered 11/11, 2011 at 1:4 Comment(2)
The LazyInitializer class, perhaps? msdn.microsoft.com/en-us/library/…. There's also Lazy<T>. See msdn.microsoft.com/en-us/library/dd997286.aspx.Mockingbird
Check out: #840288Fannyfanon
A
3

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; }
}
Asymmetric answered 11/11, 2011 at 1:7 Comment(0)
B
6

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
Bail answered 11/11, 2011 at 1:47 Comment(1)
+1; Also a good option. Many different ways to slice it :) The thread in the OP comments also show's a solution similar to Kevin's - #840288Asymmetric
A
3

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; }
}
Asymmetric answered 11/11, 2011 at 1:7 Comment(0)
C
1

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.

Coir answered 11/11, 2011 at 1:14 Comment(0)
L
1

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
    }
}
Loo answered 24/2, 2012 at 16:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.