1

I have a local string (file path) which I need to retrieve from a function only once and I'd like to make sure it's never modified again. I cannot use the const keyword because the value of my string is determined at runtime rather than compile time. So I tried using the readonly keyword instead, but Visual Studio is telling me that it is not valid for my item. How can I achieve the level of protection I want, preferably without making another class?

For simplicity and company policy, I've (drastically) shrunk down and renamed my classes and functions, but the concept is the same.

public class myClass
{
    private void myFunction()
    {
      readonly string filePath = HelperClass.getFilePath("123");

     //do stuff
    }
}

public static class HelperClass
{ 
    public static string getFilePath(string ID)
    {
        switch(ID)
        {
             case "123":
                 return "C:/123.txt";

             case "234":
                 return "C:/234.txt";

             default:
                 throw new Exception(ID + " is not supported");
        }
    }
}

=== Edit For PS2Goat ====

public class myClass
{
    protected SomeObject o;
    private virtual readonly string path;        

    public myClass(someObject o)
    {
        this.o = o;
        path = HelperClass.getFilePath(o.getID());
    }

    private virtual void myFunction()
    { 

     //do stuff
    }
}

public class myDerivedClass
{
    private override virtual readonly string path;        

    public myDerivedClass(someObject o) : base(o)
    {
        path = HelperClass.getFilePath(o.getID()); //ID will be different
    }

    private override void myFunction()
    { 

     //do different stuff
    }
}





public static class HelperClass
{ 
    public static string getFilePath(string ID)
    {
        switch(ID)
        {
             case "123":
                 return "C:/123.txt";

             case "234":
                 return "C:/234.txt";

             default:
                 throw new Exception(ID + " is not supported");
        }
    }
}

See, so this issue I'm having is that if I want to throw an exception, I'd have to catch it in the parent class' constructor for now (until that class is supported) because the parent constructor will be called before the derived constructor. So the wrong ID will be set once before the child constructor (which has the correct ID) is called.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
audiFanatic
  • 2,296
  • 8
  • 40
  • 56
  • Can't you have a property with logic in the getter/setter to prevent or restrict re-entry? – Lee Willis Jul 30 '14 at 14:56
  • Strings are immutable, so if you don't assign any other string instance to your `filePath` variable, then its value won't be changed. – Yurii Jul 30 '14 at 14:56
  • 4
    I would think about declaring a `readonly` property and initializing it only once in a constructor (or type constructor), but only if `getFilePath` method doesn't take significant time – Fedor Jul 30 '14 at 14:56
  • @Yuriy Yea, but I want to make it difficult for other coders to modify the value, it should never have to be modified within the scope of my function. – audiFanatic Jul 30 '14 at 14:58
  • In addition to the marked duplicate see http://stackoverflow.com/questions/2054761/how-to-declare-a-local-constant-in-c. – usr Jul 30 '14 at 14:59

5 Answers5

4

You cannot have readonly variables scoped within a method. Therefore it should be promoted to a readonly static field:

public class myClass
{
    private readonly static string filePath = HelperClass.getFilePath("123");
    private void myFunction()
    {    
      //do stuff
    }
}

This will cause your filePath variable to become initialized when accessing myClass for the first time. If this is not what you want, getFilePath is a long running/expensive operation, and you want to wait until myFunction is called, you can replace the implementation by System.Lazy<T>:

public class myClass
{
    private readonly static Lazy<string> filePath 
            = new Lazy<string>(() => HelperClass.getFilePath("123")));
    private void myFunction()
    {   
      string path = filePath.Value;
      //do stuff
    }
}
Bas
  • 26,772
  • 8
  • 53
  • 86
1

readonly means it can ONLY be set in the classes constructor or instatiation. So, you could change your logic to something like this:

public class myClass
{
    private readonly string _filePath;

    public myClass()
    {
        _filePath = HelperClass.getFilePath("123");
    }

    private void myFunction()
    {
      // Use your _filePath here...

     //do stuff
    }
}

public static class HelperClass
{ 
    public static string getFilePath(string ID)
    {
        switch(ID)
        {
             case "123":
                 return "C:/123.txt";

             case "234":
                 return "C:/234.txt";

             default:
                 throw new Exception(ID + " is not supported");
        }
    }
}
Belogix
  • 8,129
  • 1
  • 27
  • 32
  • not ONLY in the constructor. It can only be set during instantiation, which includes inline setting like @njenson said (this is a class-level field): `readonly string filePath = HelperClass.getFilePath("123");` – ps2goat Jul 30 '14 at 15:00
  • Now suppose that the `_filePath` property is `virtual`. I'm not sure that I could still do this. An added benefit to making the path a property (other than adding my layer of protection) rather than a field is that I can override it. However, now suppose that `myClass` is inherited by `myOtherClass` and that other class calls the constructor for `myClass`. Then, supposing that the base class (`myClass`) is not yet supported, then I'd get an exception, correct? – audiFanatic Jul 30 '14 at 15:14
  • @ps2goat In my case I cannot instantiate it inline because the function which retrieves the ID (it's not hard-coded as it is in my example) is contained in an object passed into my constructor. But it's good food for thought. – audiFanatic Jul 30 '14 at 15:17
  • @audiFanatic, I was just explaining that the ultimatum set be Belogix was incorrect. As long as the field or property is set from the constructor, you are fine. If inheriting this, you just call the base constructor from the child class's constructor. Any code called within the constructor should allow you to set this readonly member. – ps2goat Jul 30 '14 at 15:43
  • hmm, all right. Let me modify my question to better illustrate what I'm trying to do and then hopefully we can figure out a way to do this. I'll post back when the edit has been made – audiFanatic Jul 30 '14 at 15:47
  • @ps2goat Edit has been made – audiFanatic Jul 30 '14 at 16:01
1

You can just move the readonly variable to be declared outside of your function definition.

public class myClass
    {
        readonly string filePath = HelperClass.getFilePath("123");

        private void myFunction()
        {

         //do stuff with filePath
        }
    }
Nate Jenson
  • 2,664
  • 1
  • 25
  • 34
1

Since this is a variable and not a field or a property, you cannot tag it as readonly. You can however "cheat" and achieve what you want by using an anonymous type, which ensures that its properties are read-only.

For example:

    var data = new
    {
        FileName = HelperClass.getFilePath("123");
    };
Savvas Kleanthous
  • 2,695
  • 17
  • 18
0

Promote the filePath variable to field, that should fix the error. Local variables can't be readonly.

public class myClass
{
      readonly string filePath = HelperClass.getFilePath("123");
}
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • Well... Your code is identical to the question which he says doesn't work. And making it a writable field doesn't solve the issue in that he wants it only the be editable if it's empty. – siva.k Jul 30 '14 at 14:59
  • 1
    No, it is not identical to the question which he says doesn't work. He promoted it to a field rather than a local variable as it was in the question. – Tim Jul 30 '14 at 15:00
  • @siva.k My code is not same as Op's code, you may need to read it again carefully. This will work. – Sriram Sakthivel Jul 30 '14 at 15:00