- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
假设我有这个类:
public class Animal : IEquatable<Animal>
{
public string Name { get; set; }
public bool Equals(Animal other)
{
return Name.Equals(other.Name);
}
public override bool Equals(object obj)
{
return Equals((Animal)obj);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
这是测试:
var animals = new[] { new Animal { Name = "Fred" } };
现在,当我这样做时:
animals.ToList().Contains(new Animal { Name = "Fred" });
它调用正确的通用Equals
过载。问题在于数组类型。假设我这样做:
animals.Contains(new Animal { Name = "Fred" });
它调用非通用 Equals
方法。其实T[]
不公开 ICollection<T>.Contains
方法。在上述情况下 IEnumerable<Animal>.Contains
调用扩展重载,它又调用 ICollection<T>.Contains
.这是如何IEnumerable<T>.Contains
已实现:
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value)
{
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return collection.Contains(value); //this is where it gets done for arrays
}
return source.Contains(value, null);
}
所以我的问题是:
List<T>.Contains
和 T[].Contains
表现不同?换句话说,为什么前者调用 generic Equals
和 后者非泛型 Equals
即使这两个集合都是通用的?T[].Contains
执行? 编辑:为什么这很重要或者我为什么要问这个:
万一她忘记覆盖非通用Equals
,它就会跳闸 在执行 IEquatable<T>
时在这种情况下调用 T[].Contains
进行引用相等性检查。尤其是当她希望所有通用集合 对通用Equals
进行操作时。
您失去了实现 IEquatable<T>
的所有好处(尽管这对引用类型来说不是灾难)。
如评论中所述,我只想了解内部细节和设计选择。我想不出其他通用情况 非通用 Equals
将是首选,无论是 List<T>
或基于集合(Dictionary<K,V>
等)的操作。更糟糕的是,had Animal been a struct, Animal[].Contains calls the generic Equals
,所有这些使得 T[] 实现有点奇怪,开发人员应该知道的事情。
注意: Equals
的通用版本仅在类 实现 IEquatable<T>
时调用. 如果类没有实现 IEquatable<T>
, Equals
的非泛型重载无论它是否被 List<T>.Contains
调用都被调用或 T[].Contains
.
最佳答案
数组不实现 IList<T>
因为它们可以是多维的和非零基的。
但是在运行时,下限为零的一维数组会自动实现 IList<T>
和其他一些通用接口(interface)。这个运行时 hack 的目的在下面用 2 个引号详细说明。
在这里http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx它说:
In C# 2.0 and later, single-dimensional arrays that have a lower bound of zero automatically implement
IList<T>
. This enables you to create generic methods that can use the same code to iterate through arrays and other collection types. This technique is primarily useful for reading data in collections. TheIList<T>
interface cannot be used to add or remove elements from an array. An exception will be thrown if you try to call anIList<T>
method such asRemoveAt
on an array in this context.
Jeffrey Richter 在他的书中说:
The CLR team didn’t want
System.Array
to implementIEnumerable<T>
,ICollection<T>
, andIList<T>
, though, because of issues related to multi-dimensional arrays and non-zero–based arrays. Defining these interfaces on System.Array would have enabled these interfaces for all array types. Instead, the CLR performs a little trick: when a single-dimensional, zero–lower bound array type is created, the CLR automatically makes the array type implementIEnumerable<T>
,ICollection<T>
, andIList<T>
(whereT
is the array’s element type) and also implements the three interfaces for all of the array type’s base types as long as they are reference types.
深入挖掘,SZArrayHelper 是为单维零基数组提供此“hacky”IList 实现的类。
这是类的描述:
//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
//
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]".
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
// ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it.
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
并包含实现:
bool Contains<T>(T value) {
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = this as T[];
BCLDebug.Assert(_this!= null, "this should be a T[]");
return Array.IndexOf(_this, value) != -1;
}
所以我们调用下面的方法
public static int IndexOf<T>(T[] array, T value, int startIndex, int count) {
...
return EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count);
}
到目前为止一切顺利。但现在我们到了最奇怪/最有问题的部分。
考虑以下示例(基于您的后续问题)
public struct DummyStruct : IEquatable<DummyStruct>
{
public string Name { get; set; }
public bool Equals(DummyStruct other) //<- he is the man
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
public class DummyClass : IEquatable<DummyClass>
{
public string Name { get; set; }
public bool Equals(DummyClass other)
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
我在两个非 IEquatable<T>.Equals()
中都设置了异常抛出实现。
惊喜是:
DummyStruct[] structs = new[] { new DummyStruct { Name = "Fred" } };
DummyClass[] classes = new[] { new DummyClass { Name = "Fred" } };
Array.IndexOf(structs, new DummyStruct { Name = "Fred" });
Array.IndexOf(classes, new DummyClass { Name = "Fred" });
这段代码不会抛出任何异常。我们直接进入 IEquatable Equals 实现!
但是当我们尝试下面的代码时:
structs.Contains(new DummyStruct {Name = "Fred"});
classes.Contains(new DummyClass { Name = "Fred" }); //<-throws exception, since it calls object.Equals method
第二行抛出异常,堆栈跟踪如下:
DummyClass.Equals(Object obj) at System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count) at System.Array.IndexOf(T[] array, T value) at System.SZArrayHelper.Contains(T value)
现在是错误?或者这里的大问题是我们如何从我们的 DummyClass 中得到 ObjectEqualityComparer,它确实实现了 IEquatable<T>
?
因为下面的代码:
var t = EqualityComparer<DummyStruct>.Default;
Console.WriteLine(t.GetType());
var t2 = EqualityComparer<DummyClass>.Default;
Console.WriteLine(t2.GetType());
产生
System.Collections.Generic.GenericEqualityComparer
1[DummyStruct]
1[DummyClass]
System.Collections.Generic.GenericEqualityComparer
两者都使用 GenericEqualityComparer,它调用 IEquatable 方法。事实上,默认比较器调用以下 CreateComparer 方法:
private static EqualityComparer<T> CreateComparer()
{
RuntimeType c = (RuntimeType) typeof(T);
if (c == typeof(byte))
{
return (EqualityComparer<T>) new ByteEqualityComparer();
}
if (typeof(IEquatable<T>).IsAssignableFrom(c))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(GenericEqualityComparer<int>), c);
} // RELEVANT PART
if (c.IsGenericType && (c.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
RuntimeType type2 = (RuntimeType) c.GetGenericArguments()[0];
if (typeof(IEquatable<>).MakeGenericType(new Type[] { type2 }).IsAssignableFrom(type2))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(NullableEqualityComparer<int>), type2);
}
}
if (c.IsEnum && (Enum.GetUnderlyingType(c) == typeof(int)))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(EnumEqualityComparer<int>), c);
}
return new ObjectEqualityComparer<T>(); // CURIOUS PART
}
好奇的部分加粗了。显然,对于带有 Contains 的 DummyClass,我们到了最后一行,但没有通过
typeof(IEquatable).IsAssignableFrom(c)
检查!
为什么不呢?好吧,我猜它要么是错误要么是实现细节,由于 SZArrayHelper 描述类中的以下行,它因结构而异:
The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be >>exactly "T[]" - for orefs, it may be a "U[]" where U derives from T.)
所以我们现在几乎什么都知道了。剩下的唯一问题是你怎么没通过 typeof(IEquatable<T>).IsAssignableFrom(c)
检查?
PS:更准确地说,SZArrayHelper 包含的实现代码来自SSCLI20。似乎当前的实现已经改变,因为反射器显示了这个方法的以下内容:
private bool Contains<T>(T value)
{
return (Array.IndexOf<T>(JitHelpers.UnsafeCast<T[]>(this), value) != -1);
}
JitHelpers.UnsafeCast 显示来自 dotnetframework.org 的以下代码
static internal T UnsafeCast<t>(Object o) where T : class
{
// The body of this function will be replaced by the EE with unsafe code that just returns o!!!
// See getILIntrinsicImplementation for how this happens.
return o as T;
}
现在我想知道三个感叹号以及它在那个神秘的 getILIntrinsicImplementation
中到底是怎么发生的。 .
关于c# - List<T>.Contains 和 T[].Contains 的行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19887562/
我想使用 R 预定义这样的列表 DATA<-list( list(list(),list(),list()), list(list(),list(),list()), list(list(),l
如何将一个列表添加到另一个列表,返回一个列表的列表? foo :: [a] -> [a] -> [[a]] 例如,我想要的结果是: foo [1,2] [3,4] 将是 [[1,2], [3,4]]。
我还没有在这里找到类似问题的解决方案,所以我会寻求你的帮助。 有 2 个列表,其中之一是列表列表: categories = ['APPLE', 'ORANGE', 'BANANA'] test_re
这个问题不同于Converting list of lists / nested lists to list of lists without nesting (这会产生一组非常具体的响应,但无法解决
原始列表转换为 List正好。为什么原始列表的列表不能转换为 List 的列表? { // works List raw = null; List wild = raw; } {
在下面的代码中,get()被调用并将其结果分配给类型为 List> 的变量. get()返回 List>并在类型参数为 T 的实例上调用设置为 ? ,所以它应该适合。 import java.util
原始列表转换为 List正好。为什么原始列表的列表不能转换为 List 的列表? { // works List raw = null; List wild = raw; } {
在insufficiently-polymorphic 作者说: def foo[A](fst: List[A], snd: List[A]): List[A] There are fewer way
我有下面的代码有效。 class ListManipulate(val list: List, val blockCount: Int) { val result: MutableList>
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 5 年前。 Improve this ques
在 scala (2.9) 中转换列表列表的最佳方法是什么? 我有一个 list : List[List[A]] 我想转换成 List[A] 如何递归地实现这一点?或者还有其他更好的办法吗? 最佳答案
我编写了这个函数来确定给定元素是否存储在元组列表的列表中,但目前它只搜索第一个列表。我将如何搜索其余列表? fun findItem (name : command, ((x,y)::firstlis
我创建了一个类名 objectA,它有 4 个变量:约会时间;字符串文本;变量 1,变量 2 我需要创建一个 ObjectA() 列表。然后首先按时间对它们进行分组,其次按 var1,然后按 var2
我有一套说法 char={'J','A'} 和列表的列表 content = [[1,'J', 2], [2, 'K', 3], [2, 'A', 3], [3,'A', 9], [5, 'J', 9
我有以下列表 List >>> titles = new ArrayList >>> ();我想访问它的元素,但我不知道该怎么做.. 该列表有 1 个元素,它又包含 3 个元素,这 3 个元素中的
转换 List[List[Long]] 的最佳方法是什么?到 List[List[Int]]在斯卡拉? 例如,给定以下类型列表 List[List[Long]] val l: List[List[Lo
我有一个来自 Filereader (String) 的 List-List,如何将其转换为 List-List (Double):我必须返回一个包含 line-Array 的第一个 Values 的
我收集了List> 。我需要将其转换为List> 。这是我尝试过的, List> dataOne = GetDataOne(); var dataTwo = dataOne.Select(x => x
这个问题在这里已经有了答案: Cannot convert from List to List> (3 个答案) 关闭 7 年前。 我没有得到这段代码以任何方式编译: List a = new Ar
这个问题在这里已经有了答案: Cannot convert from List to List> (3 个答案) 关闭 7 年前。 我没有得到这段代码以任何方式编译: List a = new Ar
我是一名优秀的程序员,十分优秀!