【问题标题】:Dictionary with object as value以对象为值的字典
【发布时间】:2011-11-11 09:11:46
【问题描述】:

我要疯了。这可能是因为我错过了一些规则,但如果是这样,请告诉我。

我正在尝试创建一个Dictionary,其中一个字符串作为键,一个匿名对象作为值。或者,实际上我不只是在尝试,我正在这样做。 但是当我想改变对象中的特定参数时,它就出错了。

我这样声明字典:

var areas = new Dictionary<string, object>
{
  {"Key1", new {name = "Name1", today = 0, all = 0}},
  {"Key2", new {name = "Name2", today = 0, all = 0}},
    ...
}

然后我假设我可以做到这一点:

foreach (var area in areas.Keys.ToArray())
{
    var areaname = areas[area].name;
}

但 Visual Studio 不同意,拒绝编译。不过,如果我写这篇文章,一切都会如我所想 - 但这并不能真正帮助我,只会让我更加沮丧。

var areaname = new 
            {
                 name = "Nametest", today = 0, all = 0
            };
var testname = areaname.name;

我做错了什么?为什么它对我不起作用?是不是根本不可能做到这一点?

感谢您的所有回答,他们肯定让事情变得更清楚了! 我想我可能出于沮丧而将类型 object 与 C# 中的对象(即类)的概念混淆了。我会重新思考整个设计行业,可能会做一些完全不同的事情,而不是使用邪恶或肮脏的解决方案。虽然很有趣,但我认为我与 C# 的关系还没有发展到那么远!

【问题讨论】:

  • 您是否尝试使用“动态”而不是“对象”作为值类型?
  • 编译器无法解析您从字典中检索到的对象(值)的 Name 属性。对于 var 情况,编译器会放入真正的动态类型来代替 var,以帮助编译器。在这种情况下为什么需要匿名类型?当您知道所有值都应该存在哪些属性时,即它不是未知的。

标签: c# object dictionary


【解决方案1】:

这不起作用,因为您已将字典声明为 Dictionary&lt;string, object&gt;

你可以试试Dictionary&lt;string, dynamic&gt;

【讨论】:

    【解决方案2】:

    这不会编译,因为您的字典中的值是object 类型,而object 不包含属性name

    如果您使用的是 .NET 4,您可以将类型从 object 更改为 dynamic

    话虽如此,我强烈不同意这个设计决定。你真的应该为此创建一个类。

    【讨论】:

      【解决方案3】:

      匿名类型的细节在它们被创建的程序集中是不可移植的,并且在方法之外很容易†可见> 它们是在其中创建的。您应该改用匿名类型,或者如果您想采用动态方法,请使用 dynamic

      † 这里有一个邪恶的把戏,我故意不提,因为它是邪恶的。

      【讨论】:

      • 匿名类型实际上被记录为在结构上等效的在定义它们的程序集中,而不仅仅是在方法中。此外,虽然“匿名类型”很可爱,但“命名类型”更好。我有时会使用“名义类型”,尽管它具有“仅在名称中的类型”而不是“具有特定名称的类型”的不幸贬义。
      【解决方案4】:

      这不起作用,因为您已将字典声明为 Dictionary&lt;string, object&gt;

      C# 在后台为您创建一个具有这些属性的新类型(您无法访问)(这里称为匿名类型)。只有当它仍然是 var 类型时,您才能直接使用它 - 一旦将其添加到该字典,它就会被转换为 object,因此您无法再访问这些属性。

      选项 1

      您可以改为尝试Dictionary&lt;string, dynamic&gt;。 Dynamic 将能够访问这些属性 - 但这仅在 C#/.Net 4.0 中受支持

      选项 2

      您可以使用一种称为演示的技术(通过示例进行转换)来获取类型。这不是一个记录的功能,是浪费的,有点像黑客。 This is a horrible evil trick - 特别是因为它不能跨程序集工作。

      public static T Demonstrate<T>(this T demonstration, object value)
      {
          return (T)value;
      }
      

      然后您可以像这样访问原始属性:

      var area = new { name = "", today = 0, all = 0 }.Demonstrate(areas[name]);
      var areaName = value.name;
      

      选项 3 这确实是最好的方法

      写一个类。如果您喜欢 C# 编译器为您完成所有 EqualsGetHashCodeToString 的工作,您可以使用类似 ILSpy 的东西来获取原始代码。因此:

      class AreaInfo
      {
          public string Name { get; set; }
          public int Total { get; set; }
          public int All { get; set; }
      
          public override bool Equals(object obj)
          {
              var ai = obj as AreaInfo;
              if (object.ReferenceEquals(ai, null))
                  return false;
              return Name == ai.Name && Total == ai.Total && All == ai.All;
          }
      
          public override int GetHashCode()
          {
              var hc = 0;
              if (Name != null)
                  hc = Name.GetHashCode();
              hc = unchecked((hc * 7) ^ Total);
              hc = unchecked((hc * 21) ^ All);
              return hc;
          }
      
          public override string ToString()
          {
              return string.Format("{{ Name = {0}, Total = {1}, All = {2} }}", Name, Total, All);
          }
      }
      

      更正你的字典:

      var areas = new Dictionary<string, AreaInfo>       
      {       
        {"Key1", new AreaInfo() {Name = "Name1", Today = 0, All = 0}},       
        {"Key2", new AreaInfo() {Name = "Name2", Today = 0, All = 0}},       
          ...       
      };
      

      现在一切都会按照您的预期进行。

      【讨论】:

      • 您的“演示”方法(通常也称为“CastByExample”)并不可怕,因为它可能会在未来版本的语言中中断;这是完全合法的代码,那里没有实现定义的行为。这应该永远有效。然而,这很可怕,因为它不能跨程序集工作。如果您有一个由程序集 ABC.dll 创建的匿名类型字典,并且程序集 XYZ.dll 尝试通过示例进行转换,则即使匿名类型具有相同的“形状”,转换也会失败。 匿名类型仅在创建它们的程序集中是相同的。
      • @Eric - 我知道你为 MSFT 工作:),但它不属于无证行为的范畴吗?
      • @Jonathan 我去检查了 - C# 3.0 规范,7.5.10.6:“在同一个程序中,两个匿名对象初始化器在相同的顺序将产生相同匿名类型的实例。"
      • 您忘记了如何包含将 AreaInfo 从字典中提取出来的代码
      【解决方案5】:

      要让您的字典为其值具有正确的匿名类型,请执行以下操作:

      var areas=new[] {
        new {Key="Key1", Value=new {name="Name1", today=0, all=0}},
        new {Key="Key2", Value=new {name="Name2", today=0, all=0}},
      }.ToDictionary(kv => kv.Key, kv => kv.Value);
      

      然后您的代码将按预期工作:

      foreach(var area in areas.Keys.ToArray()) {
        var areaname=areas[area].name;
      }
      

      【讨论】:

      • @nawfal - 我同意这是对 OP 问题的最接近的答案,但是我不确定是否为了代码的可读性,创建一个实际的类是否会更好(尽管通常我尽量避免创建一个仅用作“临时”数据容器的类)
      • @BornToCode 是的,你是对的。使用实际的命名类型很可能是最好的选择。我应该在这里说它是 coolest 解决方案(鉴于 OP 对匿名类型很严格):)
      猜你喜欢
      • 2017-07-04
      • 1970-01-01
      • 2011-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-14
      • 2014-01-27
      • 1970-01-01
      相关资源
      最近更新 更多