如何使用扩展方法将其他数据与现有对象相关联? [英] How can additional data be associated with existing objects using extension methods?

查看:77
本文介绍了如何使用扩展方法将其他数据与现有对象相关联?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Since .NET Framework 3.5, developers have been able to add extension methods callable from instances of any object type. Extension properties have not been implemented in C#, however. Unlike extension methods, extension properties would involve storing some extra state information for individual objects.

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

However, even for extension methods, it would be highly useful in some programming scenarios to be able to access after-added/extension information for the objects on which those extension methods are called.

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

推荐答案

系统.Runtime.CompilerServices.ConditionalWeakTable 类似乎正是医生所订购的,并且似乎没有其他方法可能引起的内存泄漏担忧.以下是我对ConditionalWeakTable的使用的第一个简单包装.我将它们隐藏得更好一些(使它们更内部并且更晦涩地命名),并在它们前面放置其他方法,但这有效并且对我有很大的帮助.

The System.Runtime.CompilerServices.ConditionalWeakTable class seems to be just what the doctor ordered, and doesn't seem to entail the sort of memory leak worries that other approaches might raise. Following is my first simple wrapper around the use of ConditionalWeakTable. I will hide them a bit better (make them internal and more obscurely named) and put other methods in front of them, but this works and is a big relief and help to me.

(感谢svick,Jeppe Stig Nielsen,Tormod和user2246674帮助我解决了这个问题.)

(Thanks to svick, Jeppe Stig Nielsen, Tormod, and user2246674 for helping me think this through.)

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;
    }
}

(编辑:出于历史目的,以下为原始答案.)

(EDIT: The original answer follows, for historical purposes.)

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

The System.ComponentModel.TypeDescriptor.GetAttributes(object) method exposes a collection of System.Attribute objects that have been added to the specified object. Thus if an attribute were added to an object (but not a struct or enum), capable of storing key-value pairs, those pairs could be accessed via extension methods, hiding the storage mechanism from calling code. This is not quite as clean as extension properties would be, because of the unavoidable method pcall syntax, but is still useful in certain programming scenarios.

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

Since the object storing the data must inherit from System.Attribute, and it is unknown in advance what type of data will need to be stored, a straightforward solution is to create a class that both inherits from System.Attribute and implements IDictionary. Easy-to-use extension methods can then be made to wrap the use of this class, simplifying further the storage and retrieval of extension data.

以下是一种实现的代码:

Following is code for one such implementation:

/// <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();
    }
}

...和一些通用的扩展方法可以将其进一步包装:

... and some generic extension methods to wrap it up a bit further:

/// <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类(因此要多花些钱),另一个使用上面提供的通用扩展方法:

... and, finally, two examples of custom extension methods to use this idea in real-world code. One uses the ExtensionDataAttribute class directly (and is thus a little more nuts-and-bolts), the other uses the generic extension methods provided above:

/// <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);
    }
}

我希望这些想法有用.一个人不一定总是拥有扩展一类的奢侈,并且在某些情况下,如果不必在每个可能没有对象的对象上进行汇编和传递额外的信息,而不必每次调用一个方法,它就可以使扩展方法的设计更简洁完全是在开发人员的代码库中创建的.

I hope that these ideas have been useful. One doesn't always have the luxury of extending a class, and in some situations it may make the design of extension methods cleaner if one does not have to assemble and pass in extra information with each method call, on an object which may not have been created in the developer's codebase at all.

这篇关于如何使用扩展方法将其他数据与现有对象相关联?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆