Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 551
athos Avatar asked Nov 11 '11 01:11

athos


People also ask

Can I put in AC by myself?

While 87 percent of U.S. households use some type of air conditioning, central AC systems are the most sought-after for both convenience and accessibility. If flipping a switch for whole-house cooling is what you're after, you may wonder whether it's possible to install your own. And the answer is, you definitely can.


4 Answers

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
like image 180
Kevin Carrasco Avatar answered Oct 13 '22 00:10

Kevin Carrasco


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:

  • http://msdn.microsoft.com/en-us/library/dd997286.aspx
  • http://msdn.microsoft.com/en-us/library/dd642331.aspx
  • http://sankarsan.wordpress.com/2009/10/04/laziness-in-c-4-0-lazyt/

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:

  • http://blogs.msdn.com/b/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx
  • http://msdn.microsoft.com/en-us/library/ms750509.aspx
  • http://csharpindepth.com/Talks.aspx (search for "popsicle immutability" and you'll find a video)

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; }
}
like image 29
Merlyn Morgan-Graham Avatar answered Oct 12 '22 22:10

Merlyn Morgan-Graham


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.

like image 38
Thomas Levesque Avatar answered Oct 13 '22 00:10

Thomas Levesque


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
    }
}
like image 34
George Avatar answered Oct 12 '22 23:10

George