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

2024-01-29

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

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

这是原来的问题:如何在 C# 中的对象上添加扩展属性或设置扩展数据?


The System.Runtime.CompilerServices.ConditionalWeakTable http://msdn.microsoft.com/en-us/library/dd413870.aspxclass 似乎正是医生所吩咐的,并且似乎并不需要其他方法可能引起的内存泄漏担忧。以下是我对 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;
    }
}

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

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

我希望这些想法有用。人们并不总是有扩展类的奢侈,并且在某些情况下,如果不需要在每个方法调用中组装和传递额外的信息(在一个可能不具有完全是在开发人员的代码库中创建的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用扩展方法将附加数据与现有对象相关联? 的相关文章

随机推荐

  • 指向作为 JNA 方法参数的结构数组的指针

    我正在尝试创建一个 JNA 实现SctpDrv http www bluestop org SctpDrv 图书馆 我的问题是我不了解指向结构数组的指针 我试图寻找解决方案 但它们总是与我需要知道的略有不同 JNA 文档仅显示了一个带有指向
  • Java 中 CRTP 的替代品 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 The CRTP https en wikipedia org wiki Curiously recurring template patter
  • 在 React Native 中存储来自 json api 的大数据集合的最佳方法[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我需要制作一个具有离线功能的应用程序 可以从 json api 获取大约 10 000 个对象 估计 10mb 数据 并将它们存储在本地 以便在
  • Eloquent:带时间戳的默认值无效

    这是我的迁移架构 public function up Schema create objects function Blueprint table table gt increments id table gt timestamp tim
  • UICollectionView补充视图如何正确插入或删除

    简短的问题 有没有办法添加和删除补充视图 例如单元格和部分performBatchUpdates 块类似于insertItemsAtIndexPaths deleteItemsAtIndexPaths 甚至reloadItemsAtInde
  • 为什么 Oracle 不告诉你哪个表或视图不存在?

    如果您使用过 Oracle 您可能会收到有用的消息 ORA 00942 表或视图不存在 该消息不包含丢失对象的名称是否存在合法的技术原因 关于这是出于安全原因的争论听起来像是美国运输安全管理局 TSA 精心策划的 如果我是攻击者 我就会知道
  • 如何使用 d.items() 更改 for 循环中的所有字典键?

    我需要一些帮助来理解为什么这段代码没有按预期工作 如果想更改字典的键但保留值 他 她可以使用 d new key d pop old key 我想修改所有键 并将值保留在适当的位置 但下面的代码会跳过某些行 col2 保持不变 是因为字典是
  • 为什么管道的容量有限?

    我读到管道需要有有限的容量 但我不明白为什么 如果进程无限制地写入管道会发生什么 这是由于缓冲 管道并不 神奇 管道并不能确保所有进程都按步调一致地处理每个单独的字节或字符 相反 管道缓冲进程间输出 然后传递缓冲区 这个缓冲区大小限制就是您
  • 带滑动菜单的 Sherlock 操作栏

    我正在创建一个扩展 SherlockMapActivity 的视图和活动 并使用 SlidingMenu 库使我的主视图像 Google 一样滚动 我已经通过 XML 添加了 SlidingMenu 并且我的视图滚动完美地显示了隐藏菜单 但
  • 如何停止matlabplot3缩放

    我想使用绘制摆 改变位置 plot3功能 不幸的是 当我的点在循环中改变位置并再次绘制时 3d 图的比例也在变化 因此 x 轴取决于位置变化 取决于点的位置 它可以从 1 到 1 5 或从 1 到 3 并且 y 也会改变 只有 z 表示相同
  • 如何检测 Ag-grid 中的细胞模糊(失焦)事件

    我需要检测网格单元何时在农业网格中失去焦点 我已经检查了文档中可用的 API 但没有找到任何类似于单元格模糊的内容 https www ag grid com javascript grid events https www ag grid
  • SBT 在所有子项目之后运行任务

    我想编写一个在所有子项目任务完成后运行的任务 例如 如果我这样做 sbt a b然后在所有子项目上完成任务 a 后 我想执行任务 b 我不想对每个项目都做 a b 那可能吗 事实上 我会直接修改build sbt 我不一定必须在命令行中指定
  • 如何在 Excel 2007 中执行两个二进制数的异或计算

    我想对两个二进制数执行异或计算 例如 在工作表 1 上 Range A1 10101010 Range A2 11100010 现在我需要对A1 A2结果进行异或运算得到A3 我尝试了不同的公式的两个执行异或计算 例如 A1 A2 BITX
  • 使用 python 在不同的 virtualenv 中运行子进程

    假设我在 2 个不同的虚拟环境中安装了 2 个不同版本的应用程序 myapp v1 0 和 myapp v2 0 现在我想对它们进行比较 比较是用 python 本身编写的 最好的方法是什么 假设我可以单独运行它们并写入一个输出文件 以便稍
  • Django 按月/按季度对 Date Field() 数据进行分组

    我有一个 django 模型 其中包含日期字段 属性 class Table date models DateField value models FloatField 我正在编写一个视图 按周 月 季度和年份对这些数据进行分组 我已经硬编
  • 接口隔离原则只是单一职责原则的替代品吗?

    接口隔离原则只是单一责任原则的替代品吗 我认为如果我的类满足 SRP 则无需提取多个接口 因此 ISP 看起来像是解决方案 以防我们因某种原因必须破坏 SRP 我对吗 不 以一个类为例 该类的职责是保存数据 例如硬盘 将类分为读部分和写部分
  • @JvmDefault 以及如何添加编译器选项

    我需要做一个default voidKotlin 接口中的方法 我遇到了一个问题 有一个提示说的用法 JvmDefault只允许与 Xjvm default option 我需要在哪里写这个Xjvm default Target Annot
  • 从Unity3D代码启动android服务

    在我的 Android Unity3D 应用程序中 我需要启动一个服务 该服务将在后台运行 我不知道我该怎么做 方法启动服务 必须在活动上调用 但我不知道如何将当前的统一活动从统一脚本传递到我的 android 插件 我还没有找到任何方法以
  • 如何在ggplot中为facet_grid标签包装文本

    我一直在寻找如何换行文本 似乎应该有一种方法可以使用 labeller label wrap gen 3 但我不断收到错误 边距错误 vars 边距 未使用的参数 边距 这是我的代码的一部分 simpson by protected sta
  • 如何使用扩展方法将附加数据与现有对象相关联?

    自 NET Framework 3 5 以来 开发人员已经能够添加可从任何对象类型的实例调用的扩展方法 然而 扩展属性尚未在 C 中实现 与扩展方法不同 扩展属性将涉及为各个对象存储一些额外的状态信息 然而 即使对于扩展方法 在某些编程场景