C#
C#

help

Root Question Message

Bujju
Bujju10/9/2022
ArgumentNullException when argument is not null

I have this code:
public class Config
{
  [JsonProperty("token")]
  public string Token = "TOKEN HERE";

  [JsonProperty("source_url")]
  public string SourceUrl = "SOURCE URL HERE";

  [JsonProperty("server_url")]
  public string ServerUrl = "SERVER URL HERE";

  [JsonProperty("invite_url")]
  public string InviteUrl = "INVITE URL HERE";

  [JsonIgnore]
  private string _fileName;

  public Config(string fileName)
  {
    _fileName = fileName;

    if (!File.Exists(_fileName)) File.Create(_fileName).Close();

    var loaded = JsonConvert.DeserializeObject<Config>(File.ReadAllText(_fileName));
    if (loaded is not null)
    {
      Token = loaded.Token;
      SourceUrl = loaded.SourceUrl;
      ServerUrl = loaded.ServerUrl;
      InviteUrl = loaded.InviteUrl;
    }
    else
    {
      File.WriteAllText(_fileName, JsonConvert.SerializeObject(new Config(), Formatting.Indented));
    }
  }

  private Config()
  {
    _fileName = string.Empty;
  }

  public void Update()
  {
    File.WriteAllText(_fileName, JsonConvert.SerializeObject(this, Formatting.Indented));
  }
}
And whenever I try to access the following field, I get the following exception:
private static Config Config = new("config.json");
System.TypeInitializationException: 'The type initializer for 'class' threw an exception.'

Inner Exception:
ArgumentNullException: Path cannot be null. (Parameter 'path')
AntonC
AntonC10/9/2022
which line does it get thrown at?
AntonC
AntonC10/9/2022
in the constructor
Bujju
Bujju10/9/2022
How do I see that?
AntonC
AntonC10/9/2022
the debugger should show you
AntonC
AntonC10/9/2022
try instantiating the config in normal flow, like in the main method, if it doesn't show the line
AntonC
AntonC10/9/2022
the fact that it's statically initialized like that might be confusing it
Bujju
Bujju10/9/2022
It's at File.Create(_fileName).Close();
AntonC
AntonC10/9/2022
but in general, reading files in a constructor that's called in static context might not be the best idea
Bujju
Bujju10/9/2022
When I hover over _fileName, it says it isn't null, but that's where it throws the exception
AntonC
AntonC10/9/2022
a constructor reading files is already cursed to me
Bujju
Bujju10/9/2022
So I should move it to a method?
AntonC
AntonC10/9/2022
absolutely
AntonC
AntonC10/9/2022
that however is not the problem here
AntonC
AntonC10/9/2022
you sure it doesn't fail on the second line?
AntonC
AntonC10/9/2022
where you deserialize
AntonC
AntonC10/9/2022
ah hold on
AntonC
AntonC10/9/2022
when deserializing it calls the constructor again
AntonC
AntonC10/9/2022
probably with a null argument
Bujju
Bujju10/9/2022
It's a different constructer
AntonC
AntonC10/9/2022
which is absolutely cursed
AntonC
AntonC10/9/2022
you sure it calls private constructirs by default?
Bujju
Bujju10/9/2022
That's the only parameterless constructer
AntonC
AntonC10/9/2022
it bet it favors public ones if there are any
AntonC
AntonC10/9/2022
and tries to match parameters by name
AntonC
AntonC10/9/2022
or passes nulls
AntonC
AntonC10/9/2022
try making it public
AntonC
AntonC10/9/2022
it might actually just work
Bujju
Bujju10/9/2022
Oh wait it works now without making it public
Bujju
Bujju10/9/2022
By moving it to a method
AntonC
AntonC10/9/2022
did you turn that into a method?
AntonC
AntonC10/9/2022
ok
AntonC
AntonC10/9/2022
delay, sorry
AntonC
AntonC10/9/2022
so your deserializer most likely favored the public constructor, even though there was a parameterless one
AntonC
AntonC10/9/2022
just because it was private, the serializer decided to ignore it
AntonC
AntonC10/9/2022
and now that there's no public constructors, it's forced to use the only available one
Bujju
Bujju10/9/2022
Was the constructer called in a static context only cursed because it was reading a file, or is that still cursed
AntonC
AntonC10/9/2022
I wouldn't make such things like config static at all. I'd load them in my main or wherever, and pass around explicitly or via dependency injection
AntonC
AntonC10/9/2022
Reading a file in a constructor is definitely cursed
Bujju
Bujju10/9/2022
My Main method switches to an async task which is where the config is used
AntonC
AntonC10/9/2022
storing config in a static variable — somewhat cursed, but ok for one-off apps or if you're just learning the basics
AntonC
AntonC10/9/2022
I'd load the config, then pass the config to the task
AntonC
AntonC10/9/2022
why should the task touch globals at all? if you can make the dependency on that config explicit, do it
AntonC
AntonC10/9/2022
explicit dependencies are a good thing. they are more verbose, but they make your program a lot more tractable in the long run
Bujju
Bujju10/9/2022
So for every class that needs the config, I should make it into a non-static field and load it in the constructer?
AntonC
AntonC10/9/2022
plus, if many tasks try to read that global at once, I think class initialization is under a lock, so they'd collectively sit and wait on the IO of loading the config from disk for no reason, blocking the worker threads, potentially wasting resources
AntonC
AntonC10/9/2022
not load, pass it in as a parameter
AntonC
AntonC10/9/2022
the loading shouldn't be done in the constructor
AntonC
AntonC10/9/2022
in general, try to do the least work you can in the constructor
AntonC
AntonC10/9/2022
the best constructor is one that just assigns fields
Bujju
Bujju10/9/2022
Okay thanks
ContactFrequently Asked QuestionsJoin The DiscordBugs & Feature RequestsTerms & Privacy