我的问题是:如何比较两个数组,并对两个数组中的元素执行操作? 我使用 C#/LINQ
我想做的是:循环遍历一系列用户。另一个数组,包含某些/特定用户的规则。因此,对于在 rules 数组中具有规则的每个用户,在用户对象上增加一个字段。
我已经尝试过使用 Linq:
var array1 = context.SomeSecret.ToArray();
var array2 = anotherContext.AnotherSecret.ToArray();
(from rule in array2
from user in array1
where user.ID = rule.ID
select user).ToObserveable().Subscribe<User>(x => x.MaxRules++);
我想做的是:循环遍历一系列用户。另一个数组,包含某些/特定用户的规则。因此,对于在 rules 数组中具有规则的每个用户,更新用户对象上的字段。
这是原始代码:
var userDic = context.SomeSecret.ToDictionary(u => u.ID);
var rules = anotherContext.AnotherSecret.ToList();
foreach(var rule in rules)
{
if(userDic.ContainsKey(rule.UserID))
{
userDic[rule.UserID]++;
}
}
user.ID
和rule.UserID
是一样的。
注意:
这是"无意义"的代码
有没有">优雅"的方法可以解决这个问题?
提前谢谢。
你试图在一些陈述中做太多。这使得你的代码难以阅读、难以重用、难以更改和难以单元测试。考虑养成制作小型可重用方法的习惯。
IEnumerable<Secret> GetSecrets() {...}
IEnumerable<Secret> GetOtherSecrets() {...}
如何比较两个数组,并对两个数组中的元素执行操作?
LINQ 只能从源数据中提取数据。LINQ 无法更改源数据。若要更改源数据,应枚举使用 LINQ 提取的数据。这通常使用foreach
.
所以你有两个Secrets
序列,并且你想提取两个序列中的所有Secrets
。
定义平等
首先,您需要指定:两个序列中的密钥何时为:
Secret a = new Secret();
Secret b = a;
Secret c = (Secret)a.Clone();
很明显,a和b指的是同一个对象。尽管机密 a 和机密 c 中的所有属性和字段的值相等,但它们是不同的实例。
其效果是,如果您更改机密 a 的一个属性的值,则该值也会在机密 b 中更改。但是,秘密C保持不变。
Secret d = new Secret();
Secret e = new Secret();
IEnumerable<Secret> array1 = new Secret[] {a, d};
IEnumerable<Secret> array2 = new Secret[] {a, b, c, e};
很明显,您希望在最终结果中a
。你还需要b
,因为 a 和 b 引用同一个对象。同样清楚的是,你不希望在最终结果中使用d,也不想要e。但在您看来a
和c
是平等的吗?
您的要求中的另一个歧义:
IEnumerable<Secret> array1 = new Secret[] {a};
IEnumerable<Secret> array2 = new Secret[] {a, a, a, a, a};
您希望在最终结果中出现多少次?
相等比较器
默认情况下,a 和 c 是不同的对象,a == c
产生false
。
但是,如果要将它们定义为相等,则需要在 LINQ 中说明:不要使用相等的标准定义,使用我对相等的定义。
为此,我们需要编写一个相等比较器。或者更准确地说:创建一个实现IEqualityComparer<Secret>
的类的对象。
幸运的是,这通常非常简单。
定义:如果所有属性返回相同的值,则 Secret 类型的两个对象相等。
class SecretComparer : EqualityComparer<Secret>
{
public static IEqualityComparer<Secret> ByValue {get;} = new SecretComparer();
public override bool Equals (Secret x, Secret y)
{
... // TODO: implement
}
public override int GetHashCode (Secret x)
{
... // TODO: implement
}
实施如下
我从类EqualityComparer<Secret>
派生的原因是,而不仅仅是实现IEqualityComparer<Secret>
,是因为类EqualComparer也给了我属性Default
,如果你想在比较两个秘密时使用默认定义,这可能很有用。
LINQ:获取两个序列中的对象
有了相等比较器后,LINQ 将非常简单。 为了提取同时位于 x 和 y 中的机密,我使用了使用相等比较器的 Enumerable.Intersect 重载:
IEnumerable<Secret> ExtractDuplicateSecrets(IEnumerable<Secret> x, IEnumerable<Secret> y)
{
return x.Intersect(y, SecretComparer.ByValue);
}
就这样。要对剩余的每个密钥执行操作,请使用 foreach:
void PerformSecretAction(IEnumerable<Secret> secrets)
{
foreach (Secret secret in secrets)
{
secret.Process();
}
}
所以你的完整代码:
IEnumerable<Secret> x = GetSecrets();
IEnumerable<Secret> y = GetOtherSecrets();
IEnumerable<Secret> secretsInXandY = ExtractDuplicateSecrets(x, y);
PerformSecretAction(secretsInXandY);
或者,如果您想在一个语句中执行此操作。不确定这是否提高了可读性:
PerformSecretAction(ExtractDuplicateSecrets(GetSecrets(), GetOtherSecrets());
制作小方法的好处是:创建x和y,一个SecretComparer,提取公共秘密并对所有剩余的秘密执行操作,是大多数过程将非常小,因此易于阅读。此外,所有过程都可以重用于其他目的。你可以很容易地改变它们(相等的不同定义:只需写一个不同的比较器!),并且很容易进行单元测试。
实现机密相等
public override bool Equals (Secret x, Secret y)
{
// almost all equality comparers start with the following lines:
if (x == null) return y == null; // True if x and y both null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true; // same object
大多数时候,我们通常不希望不同的派生类相等:因此,TopSecret
(派生自 Secret)不等于Secret
。
if (x.GetType() != y.GetType()) return false;
其余的取决于您对两个机密何时相等的定义。大多数情况下,您会检查所有属性。有时您只检查一个小节。
return x.Id == y.Id
&& x.Description == y.Description
&& x.Date == y.Date
&& ...
在这里,您可以看到代码取决于您对相等的定义。也许描述检查不区分大小写:
private static IEqualityComparer<string> descriptionComparer {get;}
= StringComparer.CurrentCultureIgnoreCase;
return x.Id == y.Id
&& descriptionComparer.Equals(x.Description, y.Description)
&& ...
实现 GetHashCode
这种方法主要用于有一个快速的方法来确定两个对象不相等。一个好的GetHashCode是快速的,并且会丢弃大多数不相等的对象。
只有一个要求:如果 x 和 y 被认为是相等的,它们应该返回相同的哈希码。反之则不然:不同的对象可能具有相同的哈希码,尽管如果它们具有不同的哈希码会更好。
这个怎么样:
public override int GetHashCode (Secret x)
{
if (x == null)
return 8744523; // just a number;
else
return x.Id.GetHashCode(); // only check Id
}
在上面的代码中,我假设密钥的 Id 是相当唯一的。可能只有在更新密钥时,您才会发现两个具有相同 ID 的非相等密钥:
Secret existingSecret = this.FindSecretById(42);
Secret secretToEdit = (Secret)existingSecret.Clone();
secretToEdit.Description = this.ReadNewDescription();
现在 existingSecret 和 secretToEdit 对Id
具有相同的值,但描述不同。因此,它们是不平等的。然而,它们具有相同的哈希码。
尽管如此,到目前为止,大多数秘密都有一个唯一的 ID,GetHashCode 将是一种非常快速的方法来检测两个秘密的不同。