我的問題是:如何比較兩個陣列,并對兩者中的元素執行操作?我使用 C#/LINQ
我正在嘗試做的是:回圈遍歷一組用戶。另一個陣列,包含某些/特定用戶的規則。因此,對于在規則陣列中有規則的每個用戶,增加用戶物件上的一個欄位。
我已經嘗試過使用 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 );
我正在嘗試做的是:回圈遍歷一組用戶。另一個陣列,包含某些/特定用戶的規則。因此,對于在規則陣列中具有規則的每個用戶,更新用戶物件上的欄位。
這是原始代碼:
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是相同的。
注意:
這是“無意義”的代碼
有沒有什么“優雅”的方法來解決這個問題?
提前致謝。
uj5u.com熱心網友回復:
你試圖在幾句話中做得太多。這使您的代碼難以閱讀、難以重用、難以更改和難以進行單元測驗。考慮養成制作小型可重用方法的習慣。
IEnumerable<Secret> GetSecrets() {...}
IEnumerable<Secret> GetOtherSecrets() {...}
如何比較兩個陣列,并對兩者中的元素執行操作?
LINQ 只能從源資料中提取資料。LINQ 無法更改源資料。要更改源資料,您應該列舉使用 LINQ 提取的資料。這通常使用foreach.
因此,您有兩個 序列Secrets,并且您想提取兩個序列中的所有內容Secrets。
定義平等
首先,您需要指定:when 是兩個序列中的 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。但在你看來a,他們是c平等的嗎?
您的要求中的另一個歧義:
IEnumerable<Secret> array1 = new Secret[] {a};
IEnumerable<Secret> array2 = new Secret[] {a, a, a, a, a};
您希望最終結果有多少次?
平等比較器
默認情況下 a 和 c 是不同的物件,a == cyield false。
但是,如果您想將它們定義為相等,則需要在 LINQ 中說明:不要使用相等的標準定義,請使用我對相等的定義。
為此,我們需要撰寫一個相等比較器。或者更準確地說:創建一個實作IEqualityComparer<Secret>.
幸運的是,這通常很簡單。
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
}
Implementation is below
The reason that I derive from class EqualityComparer<Secret>, and not just implement IEqualityComparer<Secret>, is that class EqualityComparer also give me property Default, which might be useful if you want to use the default definition when comparing two Secrets.
LINQ: get objects that are in two sequences
Once you have the equality comparer, LINQ will be straightforward. To extract the Secrets that are in both x and y, I use the overload of Enumerable.Intersect that uses an equality comparer:
IEnumerable<Secret> ExtractDuplicateSecrets(IEnumerable<Secret> x, IEnumerable<Secret> y)
{
return x.Intersect(y, SecretComparer.ByValue);
}
That's all. To perform an action on every remaining Secret, use foreach:
void PerformSecretAction(IEnumerable<Secret> secrets)
{
foreach (Secret secret in secrets)
{
secret.Process();
}
}
So your complete code:
IEnumerable<Secret> x = GetSecrets();
IEnumerable<Secret> y = GetOtherSecrets();
IEnumerable<Secret> secretsInXandY = ExtractDuplicateSecrets(x, y);
PerformSecretAction(secretsInXandY);
Or if you want to do this in one statement. Not sure if this improves readability:
PerformSecretAction(ExtractDuplicateSecrets(GetSecrets(), GetOtherSecrets());
The nice thing about making small methods: creation of x and y, a SecretComparer, extract the common Secrets and perform the action on all remaining Secrets, is that most procedure will be quite small, hence easy to read. Also, all procedures can be reused for other purposes. You can easily change them (different definition of equality: just write a different comparer!), and easy to unit test.
Implement Secret Equality
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
Most of the time often we don't want that different derived classes are equal: So a TopSecret (derived from Secret) is not equal to a Secret.
if (x.GetType() != y.GetType()) return false;
The rest depends on your definition of when two Secrets are equal. Most of the time you check all properties. Sometimes you only check a subsection.
return x.Id == y.Id
&& x.Description == y.Description
&& x.Date == y.Date
&& ...
Here you can see that the code depends on your definition of equality. Maybe the Description check is case insensitive:
private static IEqualityComparer<string> descriptionComparer {get;}
= StringComparer.CurrentCultureIgnoreCase;
return x.Id == y.Id
&& descriptionComparer.Equals(x.Description, y.Description)
&& ...
Implement GetHashCode
This method is mainly used to have a fast method to determine that two objects are not equal. A good GetHashCode is fast, and throws away most unequal objects.
There is only one requirement: if x and y are considered equal, they should return the same HashCode. Not the other way round: different objects might have the same Hashcode, although it would be better if they have different HashCodes.
How about this:
public override int GetHashCode (Secret x)
{
if (x == null)
return 8744523; // just a number;
else
return x.Id.GetHashCode(); // only check Id
}
In the code above, I assume that the Id of a Secret is fairly unique. Probably only while updating a Secret you will find two non-equal Secrets with same Id:
Secret existingSecret = this.FindSecretById(42);
Secret secretToEdit = (Secret)existingSecret.Clone();
secretToEdit.Description = this.ReadNewDescription();
Now existingSecret and secretToEdit have the same value for Id, but a different Description. Hence they are not equal. Yet they have the same HashCode.
Still, by far, most Secrets will have a unique Id, GetHashCode will be a very fast method to detect that two Secrets are different.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/314653.html
