C# 比较两个数组并对匹配项执行操作

C# Compare two Arrays and perform Action on Matches

我的问题是:如何比较两个数组,并对两个数组中的元素执行操作? 我使用 C#/LINQ

我正在尝试做的事情:遍历一组用户。另一个数组,包含 some / specific 用户的规则。因此,对于规则数组中有规则的每个用户,在用户对象上增加一个字段。

我已经尝试过使用 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++);

我正在尝试做的事情:遍历一组用户。另一个数组,包含 some / specific 用户的规则。因此,对于规则数组中有规则的每个用户,更新用户对象上的字段。

这是原始代码:

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.IDrule.UserID 相同。

注:
这是“毫无意义”的代码

有什么“优雅”的方法可以解决这个问题吗?

提前致谢。

您试图在几句话中做太多事情。这使您的代码难以阅读、难以重用、难以更改和难以进行单元测试。考虑养成制作可重复使用的小方法的习惯。

IEnumerable<Secret> GetSecrets() {...}
IEnumerable<Secret> GetOtherSecrets() {...}

How can i compare two arrays, and perform an action on the Elements that are in both?

LINQ 只能从您的源数据中提取数据。 LINQ 无法更改源数据。要更改源数据,您应该枚举使用 LINQ 提取的数据。这通常使用 foreach.

来完成

所以你有两个 Secrets 序列,你想提取两个序列中的所有 Secrets

定义平等

首先,您需要指定:什么时候在两个序列中都是Secret:

Secret a = new Secret();
Secret b = a;
Secret c = (Secret)a.Clone();

很明显a和b指的是同一个对象。 Secret a和Secret c虽然所有属性和字段的值都相等,但它们是不同的实例。

效果是,如果您更改 Secret a 的其中一个属性的值,那么 Secret b 中的值也会更改。但是,Secret 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。但在您看来 ac 相等吗?

您的要求中的另一个歧义:

IEnumerable<Secret> array1 = new Secret[] {a};
IEnumerable<Secret> array2 = new Secret[] {a, a, a, a, a};

你想要多少次最终结果?

相等比较器

默认情况下 a 和 c 是不同的对象,a == c 产生 false.

但是如果你想定义它们相等,你需要在你的 LINQ 中说:不要使用相等的标准定义,使用我的相等定义。

为此我们需要编写一个相等比较器。或者更准确地说:创建一个实现 IEqualityComparer<Secret>.

的 class 对象

幸运的是,这通常很简单。

Definition: Two objects of type Secret are equal if all properties return the same value.

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
    }

执行如下

我从 class EqualityComparer<Secret> 派生而不只是实现 IEqualityComparer<Secret> 的原因是 class EqualityComparer 也给我 属性 Default,如果您想在比较两个 Secret 时使用默认定义,这可能会有用。

LINQ:获取两个序列中的对象

一旦有了相等比较器,LINQ 就会很简单。 为了提取 x 和 y 中的秘密,我使用了 Enumerable.Intersect 的重载,它使用了一个相等比较器:

IEnumerable<Secret> ExtractDuplicateSecrets(IEnumerable<Secret> x, IEnumerable<Secret> y)
{
    return  x.Intersect(y, SecretComparer.ByValue);
}

就是这样。要对每个剩余的 Secret 执行操作,请使用 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,提取公共 Secrets 并对所有剩余的 Secrets 执行操作,因为大多数过程都非常小,因此易于阅读。此外,所有过程都可以重复用于其他目的。您可以轻松更改它们(不同的相等定义:只需编写不同的比较器!),并且易于单元测试。

实施秘密平等

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

大多数时候我们不希望不同的派生 class 是相等的:因此 TopSecret(派生自 Secret)不等于 Secret

    if (x.GetType() != y.GetType()) return false;

剩下的取决于您对两个 Secret 何时相等的定义。大多数情况下,您会检查所有属性。有时你只检查一个小节。

    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被认为是相等的,它们应该return相同的HashCode。不是相反:不同的对象可能有相同的 Hashcode,尽管如果它们有不同的 HashCode 会更好。

这个怎么样:

public override int GetHashCode (Secret x)
{
    if (x == null)
        return 8744523; // just a number;
    else
        return x.Id.GetHashCode();  // only check Id
}

在上面的代码中,我假设 Secret 的 Id 是相当唯一的。可能只有在更新 Secret 时,您才会发现两个具有相同 ID 的不相等 Secret:

Secret existingSecret = this.FindSecretById(42);
Secret secretToEdit = (Secret)existingSecret.Clone();
secretToEdit.Description = this.ReadNewDescription();

现在 existingSecret 和 secretToEdit 具有相同的 Id 值,但描述不同。因此它们不相等。然而他们有相同的哈希码。

不过,到目前为止,大多数 Secret 都会有一个唯一的 ID,GetHashCode 将是一种非常快速的方法来检测两个 Secret 是否不同。