【问题标题】:EntitySet - is there a sane reason that IList.Add doesn't set assigned?EntitySet - IList.Add 没有设置分配是否有合理的原因?
【发布时间】:2011-05-31 23:18:43
【问题描述】:

向大多数列表添加项目有 3 种方法...

  • 通过直接的公共 API 方法,通常是 Add(SomeType)
  • 通过通用的IList<T>.Add(T) 接口
  • 通过非泛型IList.Add(object)接口方法

并且您通常希望它们的行为或多或少相同。然而,LINQ 的EntitySet<T> 是……在 3.5 和 4.0 上都是独一无二的; IList API 将该集标记为“已分配” - 其他两种机制 - 这个听起来微不足道,但重要的是它严重影响了样板代码中的序列化(即导致它被跳过)。

例子:

EntitySet<string> set1 = new EntitySet<string>();
set1.Add("abc");
Debug.Assert(set1.Count == 1); // pass
Debug.Assert(set1.HasLoadedOrAssignedValues, "direct"); // pass

EntitySet<string> set2 = new EntitySet<string>();
IList<string> typedList = set2;
typedList.Add("abc");
Debug.Assert(set2.Count == 1); // pass
Debug.Assert(set2.HasLoadedOrAssignedValues, "typed list"); // pass

EntitySet<string> set3 = new EntitySet<string>();
IList untypedList = set3;
untypedList.Add("abc");
Debug.Assert(set3.Count == 1); // pass
Debug.Assert(set3.HasLoadedOrAssignedValues, "untyped list"); // FAIL

现在……这让我非常惊讶;如此之多,以至于我花了 2 个多小时通过代码向上跟踪以隔离正在发生的事情。所以...

这有任何合理的理由吗?或者这只是一个错误?

(FWIW,在 3.5 中 set.Assign(set) 也存在问题,但现在已在 4.0 中修复。)

【问题讨论】:

标签: c# .net linq linq-to-sql entityset


【解决方案1】:

有趣的是,现在已经在多个版本中确定了这一点(您说 3.5 的问题已在 4.0 中修复)。 Here is a post from 2007. 4.0 中的其余 IList 方法正确地绑定到 IList&lt;T&gt; 方法。我认为有 2 种可能的解释(关于错误/功能的多样性):

  1. 这是微软尚未修复的实际错误。
  2. 这是一些其他 Microsoft 代码利用在不设置HasLoadedOrAssignedValues 的情况下添加项目的功能。

可能两者兼有 - 框架内的其他代码所依赖的错误。听起来好像有人对自己说:

没有人真的会把它转换成 IList 然后调用 Add 方法,对吧?

【讨论】:

    【解决方案2】:

    令人惊讶的是,这种差异似乎源于IList.AddIList&lt;T&gt;.Add 方法实际上具有不同的语义

    • 如果要添加的实体已经存在,IList.Add 方法将失败
    • LIst&lt;T&gt;.Add 方法删除一个实体,然后重新添加一个实体(如果已经存在)

    造成这种差异的明显原因是IList.Add 接口方法被定义为返回添加实体的索引,对于IList.Add 的典型实现,该索引始终是集合的Count,位于@ 之前987654328@.

    无论如何,由于这两个实现是故意不同的,所以作者似乎只是不小心在IList.Add 版本中省略了this.OnModified() 调用。

    【讨论】:

      【解决方案3】:

      对我来说似乎是一个错误。 ILSpy 显示了两种实现之间的差异:

      int IList.Add(object value)
      {
          TEntity tEntity = value as TEntity;
          if (tEntity == null || this.IndexOf(tEntity) >= 0)
          {
              throw Error.ArgumentOutOfRange("value");
          }
          this.CheckModify();
          int count = this.entities.Count;
          this.entities.Add(tEntity);
          this.OnAdd(tEntity);
          return count;
      }
      
      // System.Data.Linq.EntitySet<TEntity>
      /// <summary>Adds an entity.</summary>
      /// <param name="entity">The entity to add.</param>
      public void Add(TEntity entity)
      {
          if (entity == null)
          {
              throw Error.ArgumentNull("entity");
          }
          if (entity != this.onAddEntity)
          {
              this.CheckModify();
              if (!this.entities.Contains(entity))
              {
                  this.OnAdd(entity);
                  if (this.HasSource)
                  {
                      this.removedEntities.Remove(entity);
                  }
                  this.entities.Add(entity);
                  this.OnListChanged(ListChangedType.ItemAdded, this.entities.IndexOf(entity));
              }
              this.OnModified();
          }
      }
      

      看起来 IList 实现只是忽略了调用 LINQ to SQL 可能依赖于跟踪其更改的几个事件调用程序(OnListChangedOnModified)。如果这是故意的,我本来希望他们也不会拨打OnAdd

      为什么他们不简单地让 IList.Add 将值转换为 TEntity 并调用通用的 Add 方法,这超出了我的理解。

      【讨论】:

      • “为什么他们不简单地让 IList.Add 将值转换为 TEntity 并调用通用的 Add 方法,这超出了我的理解。”这几乎就是 Mono 的实现所做的。
      猜你喜欢
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多