0

I created a C# code for logging error codes.

I hardcoded the error codes into a class RecordId as static ints.

public class RecordId
{
    public static int UnknownCommand            = 100;
    public static int SoftwareVersion           = 101;
    public static int WarningError              = 110;
    public static int AbortError                = 111;
    // etc...
}

Having static int means that I can do RecordId.SoftwareVersion anywhere in my code, I don't actually need to instantiate the class RecordId, which is very convenient, since I want to be able to log things from different parts of the code by calling a Log class that also doesn't need instantiation (it just appends a message to a file)

The logging function is also static, being something like

public class Logger
{
    public static void LogExperiment(int key, string value)
    {
        // Append key and value to a hardcoded filename
    }
}

Then from anywhere in my code I can do

Logger.LogExperiment(RecordId.SoftwareVersion, "1.0");

This will just append 101 1.0 in a log file

I don't need instances of the classes, so I can log anywhere from my code.

Now, as the code grows, I don't want to modify the code every time I add a new RecordId, so I want to have a JSON file where I load the values into the class.

I modified the RecordId class to look like:

public class RecordIdNew
{
    public String UnknownCommand { get; set; }
    public String SoftwareVersion { get; set; }
    public String WarningError { get; set; }
    public String AbortError { get; set; }
}

The problem I see now, is that in order to populate this values from the JSON file I have to instantiate the class RecordId, whereas before I was using the values as static ints, and therefore I could call RecordId.SoftwareVersion

The question (which might be a bit open) is: Is there a way I can keep RecordId not instantiated, but access values that come from a JSON file.

Or if not possible, is there another structure that would allow me to do that?

Sembei Norimaki
  • 745
  • 1
  • 4
  • 11
  • 1
    You can try *static constructor* to read and assign the values – Dmitry Bychenko Nov 07 '22 at 16:40
  • When you want to use something like `RecordId.SoftwareVersion` while programming so before compiling how should that work when you only know such thing at runtime after loading the json file? And is editing a json really easier then editing the mentioned c# file? – Ralf Nov 07 '22 at 16:40
  • @Ralf. Would it be possible to load the values in the class once when the program starts, but then being able to access those values without needing an instance of the class. I understand that the loading from the JSON is needed, but then they will only exist in one particular instance, and I'll have to carry the instance everywhere as a parameter – Sembei Norimaki Nov 07 '22 at 16:42
  • @DmitryBychenko could you provide an example with the toy data I have in my question? I'm not familiar with static constructors – Sembei Norimaki Nov 07 '22 at 16:43

2 Answers2

1

You are looking for static constructor, i.e.

// Let's have class being static if you don't want to create instances
public static class RecordId
{
    // To be on the safer side of the road, let's have readonly fields:
    // once set in the static constructor they can't be changed
    public static readonly int UnknownCommand;
    public static readonly int SoftwareVersion;
    public static readonly int WarningError;
    public static readonly int AbortError;

    // Static constructor, it will be called before the first read of any field
    static RecordId() {
        //TODO: put your logic here: read the file and assign values to the fields
    }
}

Edit:

Please, have a look at the your current design, maybe you are looking for {Key, Value} pairs? E.g. Key == 100, Value == "UnknownCommand" etc.

If it's your case, try using Dictionary:

public static class RecordId {
  private static readonly Dictionary<int, string> s_Names = new();

  public IReadOnlyDictionary<int, string> Names => s_Names;

  static RecordId() {
    //TODO: Your logic here (fill in s_Names)
  }
}

usage:

int code = 100;

if (RecordId.Names.TryGetValue(code, out var name))
  Console.WriteLine($"{code} is {name}");
else
  Console.WriteLine("Unknown code");  
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • I think his intention was adding more int properties not changing the values of the existing ones. – Ralf Nov 07 '22 at 16:46
  • The design is what I intend. I actually want to obscure what the value refers to, so instead of logging "SoftwareVersion 1.0" I instead log a code for SoftwareVersion which is 101, so the log file will have "101 1.0" Of course this is just a toy example. – Sembei Norimaki Nov 07 '22 at 17:07
1

Assuming you can perfectly match up the static C# properties or fields to the values in the JSON, you can use ModuleInitializerAttribute to set the static properties.

public static class RecordId
{
    public static int UnknownCommand { get; private set; }
    public static int SoftwareVersion { get; private set; }
    public static int WarningError { get; private set; }
    public static int AbortError { get; private set; }
    // etc...

        [ModuleInitializer]
        public static void Init()
        {
            // code to read JSON
            // loop over JSON fields, matching them to
            // above fields, setting their values...
        }
}

This gives you a way to set the values at runtime, once, when the module loads (modules are groups of logical code in an assembly (reference)).

Module initializers are guaranteed to be run before any other access to the module; so if you reference, say, UnknownCommand anywhere, you will get the value that was read from the JSON. In fact, as Dmitry notes in the comments, the module init code is guaranteed to run period, even if no other code in the module is accessed at all. This could be a drawback if the code is slow or buggy, but useful in cases such as yours.

This does not give you a way to dynamically create the properties; that would require either code generation prior to compilation or access to the values at runtime via some sort of "Get" method coupled with a static dictionary.

Here's an article on the subject, and here's the original proposal on GitHub.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • 1
    It's a nice alternative to the static constructor; the (academical) drawback is that but module initializer executes too early: even if we don't use `RecordId` at all the initializer is still be executed. +1 – Dmitry Bychenko Nov 07 '22 at 17:06
  • I'll for use use RecordId, so early initialization is not a problem. I'll evaluate both options. Thanks – Sembei Norimaki Nov 07 '22 at 17:11