简体   繁体   中英

Get the declared variable name in a class

I want to do something like this - where I capture the original declaring objects variable name inside the object.

 public class Foo
    {
        private string _originalDeclarer;
        
        public Foo(string originalDeclarer=nameof(this))
        {
            _originalDeclarer = originalDeclarer;
        }

        public string OriginalDeclarer
        {
            get => _originalDeclarer;
            set => _originalDeclarer = value;
        }
    }

    public static class Bar
    {
        public static void CreateFoos()
        {
            Foo GreenFoo = new Foo();
            Foo BlueFoo = new Foo();
            
            Console.WriteLine(GreenFoo);
            Console.WriteLine(BlueFoo);
            
            //Expected output
            // "GreenFoo"
            // "BlueFoo"
        }    
    }

The above understandably doesn't work, and I understand that variable names are not stored in runtime metadata, so the general answer to this question is that it cannot be done.

That said, research leads me to several workarounds, and I am looking for the best one.

This question does a good job with the proposal:

class Self
{
    public string Name { get; }

    public Self([CallerMemberName] string name = null)
    {
        this.Name = name;
    }
}

Then:

class Foo
{
    private Self me = new Self(); // Equivalent to new Self("me")

    public void SomeMethod()
    {
        // Can't use the default here, as it would be "SomeMethod".
        // But we can use nameof...
        var joe = new Self(nameof(joe));
    }
}

I've yet to test the above if it works, but the drawback would be problematic for me.

I have - but struggling to find an earlier answer I found to this question where the names where substituted at compile time.

If anyone has ways around this problem (even if it horrifically slow) or knows how the compile-time substitution I would be very interested.

The above propose workaround would work for me if I could stop instantiation inside a method.

Edit For context here is an example where I use normal Enums - I would rather replace an enum with my own strongly typed type: ConfiguredDatabase is an enum.

private Result<DatabaseConnectionStatus> TestDatabase(ConfiguredDatabase database)
        {
            SqlDataAccessLayer sqlDataAccessLayer = DetailsStore.DataAccessLayers.TryGetbyUId(database.ToString());

            if (sqlDataAccessLayer.ConnectionDetails.DataSource == string.Empty)
            {
                return Result.Failed($"Database connection is not configured for {database}", DatabaseConnectionStatus.NoConnectionConfigured);
            }
        }

Looking at your last addition, I wouldn't recommend using an enumerated type to determine your database. There are issues if your enumerated type has a value removed at a later stage eg

enum ConfiguredDatabase
{
  Database1,
  // Database2, 
  Database3
}

Now Database3 has the same value as Database2 had in the past. If you assign fixed values for each then it will still be possible to use the value assigned to Database2 in your called code!

Instead I'd recommend sticking to some dependency injection principles here by passing the interface to a concrete class type.

Something along these lines, for example.

public interface IConfiguredDatabase
{
  string ConnectionString;
}

public Database1 : IConfiguredDatabase
{
  public Database1
  {
    ConnectionString = "Database One";
  }
  public string ConnectionString{get;set;}
}

public Database2 : IConfiguredDatabase
{
  public Database1
  {
    ConnectionString = "Database Two";
  }
  public string ConnectionString{get;set;}
}

private Result<DatabaseConnectionStatus> TestDatabase(IConfiguredDatabase database)
        {
            SqlDataAccessLayer sqlDataAccessLayer = DetailsStore.DataAccessLayers.TryGetbyUId(database.ConnectionString);

            if (sqlDataAccessLayer.ConnectionDetails.DataSource == string.Empty)
            {
                return Result.Failed($"Database connection is not configured for {database.ConnectionString}", DatabaseConnectionStatus.NoConnectionConfigured);
            }
        }

then somewhere else you call it as:

using (var d = new Database1()
{
  var result = TestDatabase(d);

...
}

I know that my example doesn't fit your code exactly, but I hope that it gives you an idea of what I'm suggesting.

If you really need to use reflection to determine what your calling property was then I would suggest that you adapt the following pattern. I use it in MVVM for ViewModel classes, hence the use of OnPropertyChanging and OnPropertyChanging Events, but I'm sure that you'll get the idea.

You may be able to adapt BaseClass.SetProperty to call other code or raise events to suit your needs.

public class BaseClass
{
    /// <summary>
    ///     Worker function used to set local fields and trigger an OnPropertyChanged event
    /// </summary>
    /// <typeparam name="T">Parameter class</typeparam>
    /// <param name="backingStore">Backing field referred to</param>
    /// <param name="value">New value</param>
    /// <param name="propertyName">Property that this value is applied to </param>
    /// <param name="onChanged">Event handler to invoke on value change</param>
    /// <param name="onChanging">Event handler to invoke on value changing</param>
    /// <returns></returns>
    protected bool SetProperty<T>(
        ref T backingStore, T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null,
        Action<T> onChanging = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false;

        onChanging?.Invoke(value);
        OnPropertyChanging(propertyName);

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    /// <summary>
    ///     INotifyPropertyChanging event handler
    /// </summary>
    public event PropertyChangingEventHandler PropertyChanging;

    /// <summary>
    ///     INotifyOnPropertyChanging implementation
    /// </summary>
    /// <param name="propertyName">Class property that is changing</param>
    protected void OnPropertyChanging([CallerMemberName] string propertyName = "")
    {
        var changing = PropertyChanging;

        changing?.Invoke(this, new PropertyChangingEventArgs(propertyName));
    }

    /// <summary>
    ///     INotifyPropertyChanged event handler
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     INotifyPropertyChanged implementation
    /// </summary>
    /// <param name="propertyName">Class property that has changed</param>
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        var changed = PropertyChanged;

        changed?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

}

public class SomeClass : BaseClass
{
    private int _propertyOne;
    private string _propertyTwo;
    
    public int PropertyOne
    {
        get=> return _propertyOne;
        set=> SetProperty(ref _propertyOne, value);
    }
    
    public int PropertyTwo
    {
        get=> return _propertyOne;
        set=> SetProperty(ref _propertyTwo, value);
    }   
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM