如何使用扩展方法将其他数据与现有对象相关联?

时间:2013-06-23 19:30:26

标签: c# methods properties attributes extension-methods

自.NET Framework 3.5以来,开发人员已经能够添加可从任何对象类型的实例调用的扩展方法。但是,扩展属性尚未在C#中实现。与扩展方法不同,扩展属性将涉及为单个对象存储一些额外的状态信息。

但是,即使对于扩展方法,在某些编程方案中,能够访问调用这些扩展方法的对象的后添加/扩展信息也非常有用。

以下是原始问题:如何在C#中添加扩展属性或以其他方式设置对象的扩展数据?

3 个答案:

答案 0 :(得分:11)

System.Runtime.CompilerServices.ConditionalWeakTable课程似乎正是医生所要求的,而且似乎并没有引起其他方法可能引起的内存泄漏问题。以下是我使用ConditionalWeakTable的第一个简单包装器。我会更好地隐藏它们(使它们更内部,更加模糊地命名)并在其前面放置其他方法,但这对我来说很有帮助和帮助。

(感谢svick,Jeppe Stig Nielsen,Tormod和user2246674帮助我思考这个问题。)

public static class ExtensionMethods
{
    private static System.Runtime.CompilerServices.ConditionalWeakTable<object, object> extendedData = new System.Runtime.CompilerServices.ConditionalWeakTable<object, object>();

    internal static IDictionary<string, object> CreateDictionary(object o) {
        return new Dictionary<string, object>();
    }

    public static void SetExtendedDataValue(this object o, string name, object value) {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
        name = name.Trim();

        IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, ExtensionMethods.CreateDictionary);
//            if (values == null)
//                extendedData.Add(o, values = new Dictionary<string, object>()); // This doesn't seem to be necessary!

        if (value != null)                 
            values[name] = value;
        else
            values.Remove(name);
    }

    public static T GetExtendedDataValue<T>(this object o, string name)
    {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
        name = name.Trim();

        IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, ExtensionMethods.CreateDictionary);
//            if (values == null) // ... nor does this!
//                return default(T);
//            else 
        if (values.ContainsKey(name))
            return (T)values[name];
        else
            return default(T);
    }

    internal static object GetExtendedDataValue(this object o, string name)
    {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
        name = name.Trim();

        IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, null);
        if (values == null)
            return null;
        else if (values.ContainsKey(name))
            return values[name];
        else
            return null;
    }
}

编辑:出于历史目的,原始答案如下。)

System.ComponentModel.TypeDescriptor.GetAttributes(object)方法公开已添加到指定对象的System.Attribute对象的集合。因此,如果将属性添加到能够存储键值对的对象(但不是结构或枚举),则可以通过扩展方法访问这些对,从而将存储机制隐藏在调用代码之外。这不像扩展属性那么干净,因为不可避免的方法pcall语法,但在某些编程方案中仍然有用。

由于存储数据的对象必须继承自System.Attribute,并且事先不知道需要存储什么类型的数据,因此直接的解决方案是创建一个既继承自System.Attribute又实现IDictionary的类。 。然后可以使用易于使用的扩展方法来包装此类的使用,从而进一步简化扩展数据的存储和检索。

以下是一个此类实现的代码:

/// <summary>
/// A System.Attribute which is also an IDictionary, useful for adding extension data to 
/// individual objects, no matter the type
/// </summary>
public class ExtensionDataAttribute : System.Attribute, IDictionary<string, object>
{
    // The dictionary wrapped by this collection, which cannot extend by System.Attribute and Dictionary at once
    private IDictionary<string, object> data = new Dictionary<string, object>();

    /// <summary>
    /// Adds this collection of extension data to the specified object; should be called only once
    /// </summary>
    /// <param name="o">The object to which to add this collection of extension data</param>
    public void AddTo(object o) {
        System.ComponentModel.TypeDescriptor.AddAttributes(o, this);
    }

    // Following are encapsulated calls to the wrapped dictionary, which should need no explanation; 
    // after accessing an ExtensionDataAttribute instance, simply use it as an IDictionary<string, object>

    public void Add(string key, object value)
    {
        data.Add(key, value);
    }

    public bool ContainsKey(string key)
    {
        return data.ContainsKey(key);
    }

    public ICollection<string> Keys
    {
        get { return data.Keys; }
    }

    public bool Remove(string key)
    {
        return data.Remove(key);
    }

    public bool TryGetValue(string key, out object value)
    {
        return data.TryGetValue(key, out value);
    }

    public ICollection<object> Values
    {
        get { return data.Values; }
    }

    public object this[string key]
    {
        get
        {
            return data[key];
        }
        set
        {
            data[key] = value;
        }
    }

    public void Add(KeyValuePair<string, object> item)
    {
        data.Add(item);
    }

    public void Clear()
    {
        data.Clear();
    }

    public bool Contains(KeyValuePair<string, object> item)
    {
        return data.Contains(item);
    }

    public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
        data.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return data.Count; }
    }

    public bool IsReadOnly
    {
        get { return data.IsReadOnly; }
    }

    public bool Remove(KeyValuePair<string, object> item)
    {
        return data.Remove(item);
    }

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
    {
        return data.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return data.GetEnumerator();
    }
}

...以及一些通用的扩展方法,将它进一步包装起来:

/// <summary>
/// Extension methods for setting and getting extension data for individual objects, no matter the type
/// </summary>
public static class ExtensionDataAttributeExtensions {

    public static void SetExtensionDataAttributeValue(this object o, string name, object value)
    {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");

        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
            if (a is ExtensionDataAttribute)
            {
                ((ExtensionDataAttribute)a)[name] = value;
                return;
            }

        ExtensionDataAttribute extensionData = new ExtensionDataAttribute();
        extensionData[name] = value;
        extensionData.AddTo(o);
    }

    public static T GetExtensionDataAttributeValue<T>(this object o, string name)
    {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");

        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
            if (a is ExtensionDataAttribute)
                return (T)((ExtensionDataAttribute)a)[name];

        return default(T);
    }

    public static object GetExtensionDataAttributeValue(this object o, string name)
    {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");

        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
            if (a is ExtensionDataAttribute)
                return ((ExtensionDataAttribute)a)[name];

        return null;
    }

    public static void RemoveExtensionDataAttributeValue(this object o, string name) {
        if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
            if (a is ExtensionDataAttribute)
                ((ExtensionDataAttribute)a).Remove(name);
    }

}

...最后,两个自定义扩展方法的例子在现实世界的代码中使用这个想法。一个直接使用ExtensionDataAttribute类(因此更多的是螺母和螺栓),另一个使用上面提供的通用扩展方法:

/// <summary>
/// Extension methods showing samples of using the ExtensionDataAttribute class directly, for use 
/// in situations where it is undesirable to include the extension methods provided with that class
/// </summary>
public static class ExtensionMethodsExample1 {

    /// <summary>
    /// Adds a description to the specified string object
    /// </summary>
    /// <param name="s">The string to describe</param>
    /// <param name="description">The description to set</param>
    public static void SetDescription(this string s, string description) {
        if (string.IsNullOrWhiteSpace(description))
            description = "";

        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(s))
            if (a is ExtensionDataAttribute) {
                ((ExtensionDataAttribute)a)["Description"] = description;
                return;
            }

        ExtensionDataAttribute extensionData = new ExtensionDataAttribute();
        extensionData["Description"] = description;
        extensionData.AddTo(s);
    }

    /// <summary>
    /// Gets the description for the specified string, if it has one; 
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static string GetDescription(this string s) {
        foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(s))
            if (a is ExtensionDataAttribute) {
                ExtensionDataAttribute eda = (ExtensionDataAttribute)a;
                if (eda.ContainsKey("Description"))
                    return eda["Description"].ToString();
                else
                    return "";
            }
        return "";
    }
}

/// <summary>
/// Extension methods encapsulating calls to extension methods provided with the ExtensionDataAttribute 
/// class, demonstrating increased ease of implementing one's own extension data
/// </summary>
public static class ExtensionMethodsExample2 {
    public static string GetDescription(this string s)
    {
        return s.GetExtensionDataAttributeValue<string>("Description");
    }

    public static void SetDescription(this string s, string description)
    {
        s.SetExtensionDataAttributeValue("Description", description);
    }
}

我希望这些想法很有用。一个并不总是具有扩展类的奢侈,并且在某些情况下,如果不必在每个方法调用上组装和传递额外信息,它可能使得扩展方法的设计更加清晰。已经在开发人员的代码库中创建了。

答案 1 :(得分:0)

当然有。您只需要一个可以与数据交互的人员访问的词典。无论何时需要数据,都可以调用TheDataStore [theObject]并返回包含所需属性的值对象。

在抽象层面,这样的属性存储是attached properties在WPF中的工作方式。

答案 2 :(得分:0)

我在想这样的事情:

static class Ext
{
  static readonly Dictionary<YourType, int> fooValues = new Dictionary<YourType, int>();

  public static int GetFoo(this YourType yt)
  {
    int value;
    fooValues.TryGetValue(yt, out value);
    return value;
  }
  public static void SetFoo(this YourType yt, int value)
  {
    fooValues[yt] = value;
  }
}

当然,这只是一个草图。如果YourType(或从中派生的任何类)覆盖EqualsGetHashCode,您可能希望为fooValues数据存储提供自定义相等比较器。 Dictionary<,>不是线程安全的,因此如果需要并行性,请使用另一个集合或使用锁。

这可能是一个问题,每个“扩展”这样的实例永远不会被垃圾收集。不管怎么说,你可以尝试使用弱引用。请参阅下面的svick评论。