2

Let's say I have a class Person with different attributes of different datatypes, and I have this code in my main method:

Person p1 = new Person
    {
        Name = inputName,
        DateOfBirth = inputDOB,
        Height = height,
        Weight = weight,
        Passport = inputPassport
    };

Now, let's say the assignation of Height throws an exception, for example an InvalidCastException, the code will stop executing, and probably log something in case I have a logging table. The issue would be that from the log I won't be able to understand exactly which line and assignation throw the exception.

Is there any way to capture the exception and being able to log (somewhere in the database) which assignation throw the exception and for which type of cast in this case (example double to DateTime).


UPDATE: At the end I resolved my problem by doing this:

  1. Updating the code so that it first creates the object (without setting the properties) and then sets the properties one line at a time.
  2. Between every line add a string that was concatenating the value of the variable just set.
  3. In my catch, I was logging the concatenating string, so having a quick look at that string basically tells me which was the last line of code that has been successfully executed before throwing the exception.
Moksud Ahmed
  • 131
  • 2
  • 11
  • you can run a validation method against all the inputs before creating the Person to eliminate the Exceptions. Convert them to the correct type and then create the Person object. – Jawad Apr 29 '20 at 17:16
  • @Jawad But that would mean creating a method for any type of cast that is needed, right? Isn't there any standard way to check and log when a cast fails and save info about which statement throw the error and for which type of conversion? – Moksud Ahmed Apr 29 '20 at 17:23
  • An implicit cast should [never throw an exception](https://stackoverflow.com/a/1537390/2791540). And your code example lacks any explicit cast. – John Wu Apr 29 '20 at 17:30
  • If those attributes are Properties, you will see set_Height() at the top of the exception stack trace. They shouldn't be fields anyway (encapsulation) – Oguz Ozgul Apr 29 '20 at 17:42
  • Or are you talking about an implicit type conversion exception? then as @JohnWu commented out. – Oguz Ozgul Apr 29 '20 at 17:46
  • @OguzOzgul They are properties, declared with the default get set, something like: public DateTime DateOfBirth { get; set; } But I don't see the get at the top of the stack trace... any idea of what could be causing this? – Moksud Ahmed Apr 29 '20 at 21:03
  • What does your catch block look like? Because when I try this (with a setter for Height which throws), I see at the top: `. . . at ConsoleApplication2.Program.Person.set_Height(Int32 value)` – Oguz Ozgul Apr 29 '20 at 21:06
  • But if they are declared as you say, there is no probability of an exception. The default `{ get; set; }` properties use backing fields (fields created by the compiler) and all they do is to return or assign this field and nothing else. – Oguz Ozgul Apr 29 '20 at 21:08

1 Answers1

1

It is a known problem. Here are some links related to it:

I have also tested your scenario using C# 7.3 (.NET 4.7.2) and C# 8 (.NET Core 3.1) and the problem still exists. Therefore if you use object initializer and one of the initializer values throws an exception then there is no way to know from the stack trace which of the initializer values caused an exception.


Workaround

Lets consider next class Person:

class Person
{
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
}

and assume that an instance of the class Person is created using the next code:

// Return type of the methods getName(), getDateOfBirth() and getAge() is object.
// Each of this methods can return invalid value, for example, method getAge()
// can return string value causing InvalidCastException.
Person p = new Person
{
    Name = (string) getName(),
    DateOfBirth = (DateTime) getDateOfBirth(),
    Age = (int) getAge()
};

If one of the initializer values (for example, casting the result of the method getName() to string data type) throws an exception we will not be able to know which of the initializer values is the reason of the exception, because line number in the stack trace will point to the line Person p = new Person.

To workaround this problem we can refuse of using object initializer and use an old style object initialization:

Person p = new Person();
p.Name = (string) getName();
p.DateOfBirth = (DateTime) getDateOfBirth();
p.Age = (int) getAge();

Now if an exception occurs we will be able to know the exact line number where the exception is occured.

Here we have to choose between readability (object initializer) and an ability to get more accurate information about the cause of the exceptions during initialization.

There is also other workarounds. For example, we can check initializer values before using them in the object initializer:


Conclusion

It is a known problem. It is still exists and haven't fixed. We can workaround it by refusing to use object initializers and using property setters (p.Name = (string) getName()). Also another workarounds can be used (for example, checking initializer values before using object initializer).

We should carefully use object initializers. If initializer values can throw an exception we should consider initializing an object using property setters (p.Name = (string) getName()), because it would simplify debugging in future.

Iliar Turdushev
  • 4,935
  • 1
  • 10
  • 23