Preserving state in an extension method

2019-02-21 19:15发布

问题:

The C# team has previously considered adding extension properties, events, etc. to C#.

Per Eric Lippert:

http://blogs.msdn.com/b/ericlippert/archive/2009/10/05/why-no-extension-properties.aspx

For these features to be useful however, they would have to be able to store some new kind of state with an object. It seems like the only way to do this would be to use a dictionary and associate each instance of an object with whatever the additional state is.

It would be useful if it were possible to copy this functionality "manually" by creating my own dictionary (and perhaps get/set extension methods). However, in order to associate a particular instance of an object with some state you would need to hash the actual reference to the object. In another language you might do this by hashing its memory location, however in C# that is not guaranteed to stay constant, and using unsafe code to accomplish this feature is far from ideal anyway.

Does anyone know if it's possible to get some hashable reference to an object that does not change as the object's internal state changes? There obviously is some internal mechanism to keep track of individual objects regardless of their memory location, but I'm not sure if that is exposed to user code.

Note: Simply hashing the object itself will not work at all, because GetHashCode() depends on an object's internal state not on which object it is.

Thanks for any insight.

回答1:

You're looking for the ConditionalWeakTable class.



回答2:

** WHOLE ANSWEr EDITED ** The properties are kept in a dictionary that uses weak reference to the objects as keys, and a dictionay with string-object pairs to store the properties and their values.

To set, get or delete properties for an object, the object is search in the weak references in the dictionary.

There could be two ways to dispose of the non-used properties:

  • check the IsAlive of the weak reference, and remove the entry in the dictionary if false
  • implement IDisposable in the "extendable" objects and call an extension method that removes the properties on the object being disposed.

I've included an optional using block in the sample code, so that you can debug and see how the Dispose calls the RemoveProperties extension method. This is of course optional, and the method would be called when the object is GC'ed.

Working sample of the idea, using WeakReference, static Dictionaries and IDisposable.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            using (PropertyLessClass plc = new PropertyLessClass())
            {
                plc.SetProperty("age", 25);
                plc.SetProperty("name", "John");
                Console.WriteLine("Age: {0}", plc.GetProperty("age"));
                Console.WriteLine("Name: {0}", plc.GetProperty("name"));
            }
            Console.ReadLine();
        }
    }
}

public class PropertyLessClass : IDisposable
{
    public void Dispose()
    {
        this.DeleteProperties();
    }
}

public static class PropertyStore
{
    private static Dictionary<WeakReference, Dictionary<string, object>> store
        = new Dictionary<WeakReference, Dictionary<string, object>>();

    public static void SetProperty(this object o, string property, object value)
    {
        var key = store.Keys.FirstOrDefault(wr => wr.IsAlive && wr.Target == o);
        if (key == null)
        {
            key = new WeakReference(o);
            store.Add(key, new Dictionary<string, object>());
        }
        store[key][property] = value;
    }

    public static object GetProperty(this object o, string property)
    {
        var key = store.Keys.FirstOrDefault(wr => wr.IsAlive && wr.Target == o);
        if (key == null)
        {
            return null; // or throw Exception
        }
        if (!store[key].ContainsKey(property))
            return null; // or throw Exception
        return store[key][property];
    }

    public static void DeleteProperties(this object o)
    {
        var key = store.Keys.FirstOrDefault(wr => wr.IsAlive && wr.Target == o);
        if (key != null)
        {
            store.Remove(key);
        }
    }
}