2

I want to store some backing fields of Properties declared in derived classes in protected Hashtable contained in base class. The usage of this mechanism in derived classes has to beas simple as possible.

So, can I use MethodBase.GetCurrentMethod() to provide information about calling property (getter - properties are read-only), so it can be recognized as the one and only property that has access to this particular backing field?

EDIT:

Basically, I want to implement pattern:

private SomeClass _someProperty = null;
private SomeClass SomeProperty
{
    if (_someProperty == null)
    {
        _someProperty = new SomeClass();
    }
    return _someProperty;
}

to look something like this:

private SomeClass SomeProperty
{
    return GetProperty(delegate
    {
        var someProperty = new SomeClass();
        return someProperty;
    };
}

And in base class

    private System.Collections.Hashtable _propertyFields = new System.Collections.Hashtable();

    protected T GetProperty<T>(ConstructorDelegate<T> constructorBody)
    {
        var method = new System.Diagnostics.StackFrame(1).GetMethod();
        if (!_propertyFields.ContainsKey(method))
        {
            var propertyObject = constructorBody.Invoke();
            _propertyFields.Add(method, propertyObject);
        }
        return (T)_propertyFields[method];
    }

    protected delegate T ConstructorDelegate<T>();

The reason I want to do this is to simplify the usage of properties. I use private properties to create some objects and use them around the class. But when I store their backing fields in the same class, I have the same access to them as to the properties, so I (means user who would create some derived classes in the future) could accidently use backing field instead of the property, so I wanted to restrict access to backing field, while allow to create object and use it.

I tried to use ObsoleteAttribute on the backing fields like this:

    [Obsolete("Don't use this field. Please use corresponding property instead.")]
    private SomeClass __someProperty;
    private SomeClass _someProperty
    {
#pragma warning disable 0618 //Disable Obsolete warning for property usage.
        get
        {
            if (__someProperty== null)
            {
                __someProperty = new SomeClass();
            }
            return __someProperty ;
        }
#pragma warning restore 0618 //Restore Obsolete warning for rest of the code.
    }

But, firstly, I cannot force the user to use this pattern, and secondly, it's to much code to write in derived class, which, as I metioned above, I want to be as simple as possible.

Szybki
  • 1,083
  • 14
  • 29
  • 1
    How about some example usage? It almost sounds like an [`ExpandoObject`](https://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject(v=vs.110).aspx) might suit – weston Jul 26 '16 at 13:13
  • Why do you want to store backing fields in a base class? Your derived class should expose its private members through public properties and the private fields should remain private to the derived class. Are you trying to maintain or share state between derived objects? – Ian R. O'Brien Jul 26 '16 at 13:32
  • @IanR.O'Brien, I edited the question. – Szybki Jul 26 '16 at 13:53
  • 1
    @weston, I edited the question. I also forgot to mention, I'm using .NET 2.0 in VS2010, so fancy classes such as ExpandoObject, are rather not allowed. – Szybki Jul 26 '16 at 13:54
  • This is much better. I have questions about your implementation but I'm afraid I would be taking this question into a different direction, so you may want to consider posting your code to http://codereview.stackexchange.com/ for more comments, or posting your code into a different question. In any case I hope someone can give you what you are looking for in this specific question. – Ian R. O'Brien Jul 26 '16 at 14:22
  • Interesting, why are you stuck on .net 2.0? It was old 10 years ago, never mind at the time of VS2010. – weston Jul 26 '16 at 15:04
  • @weston I guess this is common issue. When you start your own projects, you have full control over them. But if you are working on someone else's project that has been started ten years ago and you have to work the priorities out first, you will see there's actually no time to get into new technologies. One of my business partners said once that it's not about the familiarity with new technologies, it's about the ability to work things out using what is available. – Szybki Jul 26 '16 at 21:21
  • But VS2010 supports .net 4.0 so it's usually as simple as selecting that framework version in the project properties. I think your business partner doesn't know it's harder to ask the community a question and have them restrict their answers to 10 year old tech. In fact it's unacceptable to ask that of us when you have no valid reason to be so far behind, because such an arbitrary restriction is going to make it unlikely to be of use for future visitors. – weston Jul 26 '16 at 23:09

1 Answers1

2

Neither MethodBase nor MemberInfo do not properly overrides Equals and GetHashCode functions, but uses default RuntimeHelpers.GetHashCode and RuntimeHelpers.Equals. So you will only be able to compare same instance, but not same content. In most cases this will be enough as runtime caches that instances to reuse them. But there is no guarantee this will work stable.

As you working with metadata, use something that will identify it uniquely. For example, MemberInfo.MetadataToken. You could write your own comparer and use it inside hashtable:

public class MethodBaseComparer : IEqualityComparer<MethodBase>
{
    public bool Equals(MethodBase x, MethodBase y)
    {
        if (ReferenceEquals(x, y))
            return true;

        if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
            return false;

        return x.MetadataToken.Equals(y.MetadataToken) &&
               x.MethodHandle.Equals(y.MethodHandle);
    }

    public int GetHashCode(MethodBase obj)
    {
        return (obj.MetadataToken.GetHashCode() * 387) ^ obj.MethodHandle.GetHashCode();
    }
}

It not a good idea to restrict access via reflection to some members as other trusted code can use reflection to access other private data outflanking your checks. Consider restrict access via redesigning your classes.

Also take a look at Code Access Security.

Update according to your edit.

You told your properties are read-only. I guess, simply declaring them as readonly is not your option. Looks like you want delayed initialization for properties values. In that case you will not able to declare them as readonly. Right?

Or maybe you can?

Take a look at Lazy<T> class. It's not available in dotnet 2.0, but you can easily implement it or even take any existing implementation (just replace Func<T> with your delegate). Example usage:

public class Foo
{
    private readonly Lazy<int> _bar = new Lazy<int>(() => Environment.TickCount, true);
    //            similar to your constructorBody - ^^^^^^^^^^^^^^^^^^^^^^^^^^^

    private int Bar
    {
        get { return this._bar.Value; }
    }

    public void DoSomethingWithBar(string title)
    {
        Console.WriteLine("cur: {0}, foo.bar: {1} <- {2}",
                          Environment.TickCount,
                          this.Bar,
                          title);
    }
}

Pros:

  1. It's a lazy initialization as you wish. Let's test it:

    public static void Main()
    {
        var foo = new Foo();
    
        Console.WriteLine("cur: {0}", Environment.TickCount);
    
        Thread.Sleep(300);
        foo.DoSomethingWithBar("initialization");
    
        Thread.Sleep(300);
        foo.DoSomethingWithBar("later usage");
    }
    

    Output will be something like this:

    cur: 433294875
    cur: 433295171, foo.bar: 433295171 <- initialization
    cur: 433295468, foo.bar: 433295171 <- later usage
    

    Note, value initialized on first access and not changed later.

  2. Properties are write-protected by a compiler - _bar field is readonly and you have no access to internal fields of Lazy<T>. So, no any accidental backing field usage. If you try you will get compilation error on type mismatch:

    CS0029 Cannot implicitly convert type System.Lazy<SomeClass> to SomeClass

    And even if you access it via this._bar.Value, nothing terrible would happen and you will get a correct value as if you access it via this.Bar property.

  3. It is much more simpler, faster and easier to read and maintain.

  4. Thread safety out of the box.

Cons: — (I didn't found)

Few cents about your hashtable-based design:

  1. You (or someone who will maintain your code) can accidentally (or advisedly) access and/or modify either whole hashtable or it's items as it is just a usual private property.
  2. Hashtable is a minor performance hit + getting stacktrace is a huge performance hit. However I don't know if it is critical, depends on how often you access your properties.
  3. It would be hard to read and maintain.
  4. Not thread safe.
lorond
  • 3,856
  • 2
  • 37
  • 52
  • This looks nice, but I'm able to get MethodBase (either by MethodBase.GetCurrentMethod() in calling method (property getter) or by new System.Diagnostics.StackFrame(1).GetMethod() in method that reads value. But how can I get the MethodInfo? – Szybki Jul 26 '16 at 12:56
  • @Szybki `MetadataToken` is member of `MemberInfo`, which is parent of `MethodBase` (and contains `MethodHandle`), which is parent of `MethodInfo`. However I've changed answer to remove confusion. – lorond Jul 26 '16 at 13:01
  • +1 and a tiny fix: `if (x == null || y == null) return false;` should more correctly be `if (x == null) return y == null;`, or even better `if (object.ReferenceEquals(x, null)) return object.ReferenceEquals(y, null);`. – vgru Jul 26 '16 at 13:16
  • @Groo Thanks. `null == null` case will be handled by first `if`, but I agreed with `ReferenceEquals` instead of `==`. – lorond Jul 26 '16 at 13:20
  • @lorond: you're right, I read the code too quickly, no need to check `y` once `x` is known to be `null`. – vgru Jul 26 '16 at 13:22
  • @lorond Please see my edited question. I appreciate your help, but I don't understand what do you mean about restricting access via reflection. – Szybki Jul 26 '16 at 14:16