1

I couldn't figure out how to formulate the title any better. I see quite a few posts with similar titles but which discuss entirely different stuff.

So here we go. The actual on-ground situation is complex, but I'll try to post an absolute minimalistic example to describe it.

Let's say we have a class named Animal:

class Animal
{
  public void Run()
  {
    try
    {
      //try running
    }
    catch(Exception e)
    {
      MessageBox.Show(this.SomeCleverWayOfGettingPropertyName() + " failed to run");
    }
  }
}

Now I define several properties of Animal type in another class:

class Zoo
{
  public Animal Zebra {get; set;}
  public Animal Lion {get; set;}
  public Animal Rhino {get; set;}

  public void RunAll()
  {
    Zebra.Run();
    Lion.Run();
    Rhino.Run();
  }
}

What do I write in place of SomeCleverWayOfGettingPropertyName() to let it show name of the animal (that is name of the declared property), like "Zebra failed to run".

As I said, the actual situation is more complex, so kindly avoid answers like, "why don't you redesign your entire code base and instead try X". My hope is to find something in System.Reflection to find out the calling member's name, but I haven't found anything like that yet.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • @CodeCaster: hmm... that would be a road bump. Problem is that there are just too many instances of `Animal` all around, so i was trying to create a centralized location to catch exceptions in `Run`. Does it help if we know that it will always be called on properties and never local variables, e.g. by force-casting it to `PropertyInfo` or something? – dotNET Oct 17 '21 at 06:34

4 Answers4

1

Ideally you would rethink your problem, and possibly catch outside of the run

Depending on your exact needs, an expression might work.. However it really is a terrible solution, if you went to all the effort you might as well catch outside, or just pass the member name in.

Given

public class Animal
{
   public void Run()
   {
      Console.WriteLine("Running");
   }
}

public static class MemberInfoGetting
{
   public static void Run<T>(this Expression<Func<T>> memberExpression) where T : Animal
   {
      var expressionBody = (MemberExpression)memberExpression.Body;
      try
      {

         var animal = Expression.Lambda<Func<Animal>>(expressionBody).Compile()();
         animal.Run();
         throw new Exception("bob");
      }
      catch
      {
         Console.WriteLine($"{expressionBody.Member.Name} : failed to run");
      }
   }
}

Usage

public static Animal Rhino { get; set; } = new Animal();

public static void Main()
{
   MemberInfoGetting.Run(() => Rhino);
}

Output

Running
Rhino : failed to run
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • Yep. It requires me to edit all the places from where `Run` has been called, something that I want to avoid. But thanks, that's some clever thinking. :) – dotNET Oct 17 '21 at 07:07
1

This is basically not possible with this approach. What happens when you call Zebra.Run():

  • Runtime calls the auto-generated get_Zebra() method, putting the Zebra's Animal instance pointer on the stack.
  • Runtime calls the Animal.Run() instance method.

All variable/property info about where that instance came from is pretty much gone at that point.

Now Animal.Run() doesn't know it's being called on an instance that came from a property, and there's no guarantee it will be. It could as well be a local, a method parameter or a new()ed instance, one from a factory or a collection element. You'll have to pass this info yourself.

Alternatively, if it's for error handling, it may be easier than you think without having to resolve to compiler magic or expensive expression refactoring:

In your exception handler, log the relevant properties that identify the Animal instance. Combined with the stack trace, this should give you enough information.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 1
    Thanks. That's some insightful information. What I didn't mention in my post (to make it minimal) was that my properties were of type `RelayCommand` (MVVM Light). Since C# internally converts properties into `get_*` and `set_*` methods, simply calling `.Method.Name` on my Aniaml's `Run` method returned `get_Zebra` (as you have mentioned) which was enough for my purpose. Thanks a lot for the input. – dotNET Oct 17 '21 at 09:10
0

you can try this:

class Animal
{
  public void Run([CallerMemberName] string caller = null)
  {
    try
    {
      //try running
    }
    catch(Exception e)
    {
      MessageBox.Show(caller + " failed to run");
    }
  }
}
fmansour
  • 555
  • 3
  • 17
0

The only way to reasonable do this is change RunAll() such that it monitors each call, to the now modified run

class Animal
{
    static readonly Random rng = new Random();
    public bool Run()
    {
        if (rng.NextDouble() < 0.5)
        {
            return false;
        }
        return true;
    }
}

class Zoo
{
    ...
    public void RunAll()
    {
        try
        {

            if (!Zebra.Run())
            {
                throw new Exception(nameof(Zebra));
            }
            if (!Lion.Run())
            {
                throw new Exception(nameof(Lion));
            }
            if (!Rhino.Run())
            {
                throw new Exception(nameof(Rhino));
            }
        }
        catch (Exception ex)
        {

            Debug.WriteLine($"{ex.Message} failed to run.");
        }
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133