在客户端和 WCF 服务器上的字典中用作键的对象引用
Object references used as keys in dictionary on client and WCF server
我正在使用 WCF 编写客户端和服务,但我怀疑目前存在多个问题。
从下面的代码可以看出,过程如下:客户端请求一些数据,服务生成这些数据并在 DTO 对象中 returns。然后客户端尝试在返回的字典中进行查找,但这会抛出 KeyNotFoundException。
此外,在此之前服务器上的测试失败(如果未注释),因为输入参数列表 allBranches 不再包含分支 currentBranch,它在方法调用的客户端进行。
谁能告诉我这段代码中发生了什么,为什么它先在服务器端爆炸,然后在客户端爆炸?
Shared class definitions
------------------------
[DataContract(IsReference = true)]
public class Branch
{
public Branch(int branchId, string name)
{
BranchId = branchId;
Name = name;
}
[DataMember]
public int BranchId { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Department
{
public string Name { get; set; }
// a few other properties, both primitives and complex objects
}
[DataContract]
public class MyDto
{
[DataMember]
public IDictionary<Branch, List<Department>> DepartmentsByBranch { get; set; }
[DataMember]
public Branch CurrentBranch { get; set; }
// lots of other properties, both primitives and complex objects
}
Server-side
--------------------------------
public CreateData(List<Branch> allBranches, Branch currentBranch)
{
// BOOM: On the server side, currentBranch is no longer contained in allBranches (presumably due to serialization and deserialization)
if (!branches.Contains(branchToOpen))
{
throw new ArgumentException("allBranches no longer contain currentBranch!");
}
// Therefore, I should probably not do the following, expecting to use currentBranch as a key in departmentsByBranch later on
var departmentsByBranch = branches.ToDictionary(branch => branch, branch => new List<Department>());
return new MyDto
{
DepartmentsByBranch = departmentsByBranch,
CurrentBranch = departmentsByBranch,
};
}
Client-side (relevant code only)
--------------------------------
var service = new ServiceProxy(); // using a binding defined in app.config
var allBranches = new List<Branch>
{
new Branch(0, "First branch"),
new Branch(1, "Second branch"),
// etc...
};
var currentBranch = allBranches[0];
MyDto dto = service.CreateData(allBranches, currentBranch);
var currentDepartments = dto.DepartmentsByBranch[currentBranch]; // BOOM: Generates KeyNotFoundException
编辑:我按照下面乔恩的出色回答进行了以下操作(解决了所有问题):
通过给每个 属性 一个私有 setter.
使分支不可变
- 在字典中用作键的每个 class 都应该是不可变的,或者至少具有根据不可变属性计算的哈希码。
实现了 IEquatable + Object.Equals 和 GetHashCode 的覆盖,后者根据这个 SO-answer (link)
只需测试相等的 属性 值即可实现 IEquatable,
public bool Equals(Branch other)
{
return other != null && ((BranchId == other.BranchId) && (Name == other.Name));
}
您的代码失败的原因是您的客户端中有两个单独的 Branch
实例:一个您在本地创建的实例 (currentBranch
) 和一个从服务器接收并隐式创建的实例通过 WCF(在 dto.DepartmentsByBranch
内)。您没有指定这两个实例是 "the same thing",因此就字典而言,它从未见过您正在谈论的 currentBranch
。
您需要为 Branch
提供 IEquatable<Branch>
的正确实现 - 这同样适用于您用作字典键的所有 类。
注意 "proper implementation" means
If you implement IEquatable<T>
, you should also override the base
class implementations of Object.Equals(Object)
and GetHashCode
so that
their behavior is consistent with that of the IEquatable<T>.Equals
method.
我正在使用 WCF 编写客户端和服务,但我怀疑目前存在多个问题。
从下面的代码可以看出,过程如下:客户端请求一些数据,服务生成这些数据并在 DTO 对象中 returns。然后客户端尝试在返回的字典中进行查找,但这会抛出 KeyNotFoundException。
此外,在此之前服务器上的测试失败(如果未注释),因为输入参数列表 allBranches 不再包含分支 currentBranch,它在方法调用的客户端进行。
谁能告诉我这段代码中发生了什么,为什么它先在服务器端爆炸,然后在客户端爆炸?
Shared class definitions
------------------------
[DataContract(IsReference = true)]
public class Branch
{
public Branch(int branchId, string name)
{
BranchId = branchId;
Name = name;
}
[DataMember]
public int BranchId { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Department
{
public string Name { get; set; }
// a few other properties, both primitives and complex objects
}
[DataContract]
public class MyDto
{
[DataMember]
public IDictionary<Branch, List<Department>> DepartmentsByBranch { get; set; }
[DataMember]
public Branch CurrentBranch { get; set; }
// lots of other properties, both primitives and complex objects
}
Server-side
--------------------------------
public CreateData(List<Branch> allBranches, Branch currentBranch)
{
// BOOM: On the server side, currentBranch is no longer contained in allBranches (presumably due to serialization and deserialization)
if (!branches.Contains(branchToOpen))
{
throw new ArgumentException("allBranches no longer contain currentBranch!");
}
// Therefore, I should probably not do the following, expecting to use currentBranch as a key in departmentsByBranch later on
var departmentsByBranch = branches.ToDictionary(branch => branch, branch => new List<Department>());
return new MyDto
{
DepartmentsByBranch = departmentsByBranch,
CurrentBranch = departmentsByBranch,
};
}
Client-side (relevant code only)
--------------------------------
var service = new ServiceProxy(); // using a binding defined in app.config
var allBranches = new List<Branch>
{
new Branch(0, "First branch"),
new Branch(1, "Second branch"),
// etc...
};
var currentBranch = allBranches[0];
MyDto dto = service.CreateData(allBranches, currentBranch);
var currentDepartments = dto.DepartmentsByBranch[currentBranch]; // BOOM: Generates KeyNotFoundException
编辑:我按照下面乔恩的出色回答进行了以下操作(解决了所有问题):
通过给每个 属性 一个私有 setter.
使分支不可变- 在字典中用作键的每个 class 都应该是不可变的,或者至少具有根据不可变属性计算的哈希码。
实现了 IEquatable + Object.Equals 和 GetHashCode 的覆盖,后者根据这个 SO-answer (link)
只需测试相等的 属性 值即可实现 IEquatable,
public bool Equals(Branch other)
{
return other != null && ((BranchId == other.BranchId) && (Name == other.Name));
}
您的代码失败的原因是您的客户端中有两个单独的 Branch
实例:一个您在本地创建的实例 (currentBranch
) 和一个从服务器接收并隐式创建的实例通过 WCF(在 dto.DepartmentsByBranch
内)。您没有指定这两个实例是 "the same thing",因此就字典而言,它从未见过您正在谈论的 currentBranch
。
您需要为 Branch
提供 IEquatable<Branch>
的正确实现 - 这同样适用于您用作字典键的所有 类。
注意 "proper implementation" means
If you implement
IEquatable<T>
, you should also override the base class implementations ofObject.Equals(Object)
andGetHashCode
so that their behavior is consistent with that of theIEquatable<T>.Equals
method.