250123 ‐ handle finding CA to update - cywongg/2025 GitHub Wiki
The difference between FirstOrDefault
and First
in LINQ revolves around how they handle scenarios where no matching element is found in a collection.
- Returns the first element in a sequence that matches the condition, or a default value if no such element is found.
-
Default value depends on the type:
- For reference types (e.g., classes), the default value is
null
. - For value types (e.g.,
int
,float
), the default value is the type's default (e.g.,0
forint
,false
forbool
).
- For reference types (e.g., classes), the default value is
Example:
var numbers = new List<int> { 1, 2, 3 };
var result = numbers.FirstOrDefault(n => n > 3); // No element > 3, so result is 0 (default for int)
- Safe to use when you're not sure if the sequence contains matching elements and want to avoid exceptions.
- Returns the first element in a sequence that matches the condition.
- If no element matches, it throws an
InvalidOperationException
.
Example:
var numbers = new List<int> { 1, 2, 3 };
var result = numbers.First(n => n > 3); // Throws InvalidOperationException because no element > 3
- Use
First
when you are confident that the sequence always contains a matching element.
The default value is determined by the type of the elements in the collection, and it is set by the runtime using the type's default constructor. This is controlled by the framework and follows these rules:
-
Reference types: Default is
null
. -
Value types: Default is the type's default value.
- Numeric types (e.g.,
int
,double
) →0
-
bool
→false
- Custom structs → A new instance of the struct with all fields set to their defaults.
- Numeric types (e.g.,
-
Reference Type Example:
var strings = new List<string> { "apple", "banana" }; var result = strings.FirstOrDefault(s => s == "cherry"); Console.WriteLine(result == null); // Output: True
-
Value Type Example:
var numbers = new List<int> { 1, 2, 3 }; var result = numbers.FirstOrDefault(n => n > 5); Console.WriteLine(result); // Output: 0 (default for int)
-
Custom Struct Example:
struct Point { public int X; public int Y; } var points = new List<Point>(); var defaultPoint = points.FirstOrDefault(); Console.WriteLine(defaultPoint.X); // Output: 0 Console.WriteLine(defaultPoint.Y); // Output: 0
-
Use
FirstOrDefault
:- When it's acceptable for no result to be found, and you want to avoid an exception.
- When working with optional data (e.g., querying a database where results may or may not exist).
-
Use
First
:- When you expect the collection to always contain at least one matching element.
- When the absence of a result should indicate a bug or invalid state (and throwing an exception is desirable in such cases).
Feature | First |
FirstOrDefault |
---|---|---|
Returns | First matching element | First matching element or default value |
Throws Exception | Yes, if no matching element is found | No, returns default value instead |
Default Value | N/A (throws exception) |
null (reference types) or type default (value types) |
Use Case | When you are certain the element exists | When the element may not exist |
Let me know if you need further clarification! 😊 You're right—while the earlier solutions are flexible and extensible, they might seem overly complicated for your needs. Let’s focus on simplicity and clean code principles, while keeping the solution straightforward.
We can make the FindCorporateActionToUpdate
method clean and simple by using a single method with clear and concise logic, avoiding unnecessary abstractions.
Here’s how we can do it:
private CompositeCorporateAction FindCorporateActionToUpdate(ISelectedCorporateAction corporateAction)
{
// Use pattern matching to determine the type and perform the appropriate lookup
return corporateAction switch
{
SelectedCa => FindInPdisOrCas(corporateAction),
SelectedRfactor => FindInCasOnly(corporateAction),
_ => null // Default case for unsupported types
};
}
// Lookup for SelectedCa: Look in _pdis first, then _cas
private CompositeCorporateAction FindInPdisOrCas(ISelectedCorporateAction corporateAction)
{
return _pdis.FirstOrDefault(pdi =>
pdi.UdlEliotCode == corporateAction.UdlEliotCode &&
pdi.ExerciseDate == corporateAction.ExerciseDate)
?? _cas.FirstOrDefault(ca =>
ca.UdlEliotCode == corporateAction.UdlEliotCode &&
ca.ExerciseDate == corporateAction.ExerciseDate);
}
// Lookup for SelectedRfactor: Only look in _cas
private CompositeCorporateAction FindInCasOnly(ISelectedCorporateAction corporateAction)
{
return _cas.FirstOrDefault(ca =>
ca.UdlEliotCode == corporateAction.UdlEliotCode &&
ca.ExerciseDate == corporateAction.ExerciseDate);
}
-
Use Pattern Matching (
switch
Expression):- Instead of
if-else
blocks, we use aswitch
expression to cleanly handle type-based logic. - This eliminates repetitive
if-else
conditions while keeping the logic concise.
- Instead of
-
Encapsulate Lookup Logic:
- The two private methods (
FindInPdisOrCas
andFindInCasOnly
) handle the specific lookup logic for each type. - These methods are straightforward and self-explanatory.
- The two private methods (
-
Default Case:
- The
_ => null
default ensures that unsupported types simply returnnull
without throwing errors.
- The
-
Simplicity: We minimize complexity by avoiding unnecessary abstractions like dictionaries or delegates, focusing only on the task at hand.
-
Readability: The code is easy to read and understand, with clear separation of responsibilities between the main
FindCorporateActionToUpdate
method and the helper methods. -
Maintainability: If new types (e.g.,
SelectedXyz
) are introduced, adding a new case to theswitch
expression and implementing a corresponding helper method is straightforward.
-
Minimal Code: The logic is compact yet clear.
-
No Over-Engineering: We avoid unnecessary abstractions or advanced patterns since the requirements are straightforward.
-
Type-Safe: The
switch
expression ensures type safety, and the compiler will warn you if a new type is added but not handled.
Here’s how the FindCorporateActionToUpdate
method integrates with the rest of the refactored code:
public async Task UpdateCorporateActionUploadStatusAsync<T>(ICollection<T> corporateActions, int feeType)
where T : ISelectedCorporateAction
{
foreach (var corporateAction in corporateActions)
{
// Get basket instrument IDs
var basketInstrumentIds = MainWindow
.GetInstrumentByUdlEliot(corporateAction.UdlEliotCode)
.Select(instrument => instrument.BasketInstrumentID)
.ToList();
// Get updated fees
var updatedFees = GetUpdatedFees(factorDataSetsByBasketInstrumentId, basketInstrumentIds, feeType);
// Find the corporate action to update
var corporateActionToUpdate = FindCorporateActionToUpdate(corporateAction);
// Update status if the corporate action exists
if (corporateActionToUpdate != null)
{
await UpdateCorporateActionStatusAsync(corporateAction, corporateActionToUpdate, updatedFees, basketInstrumentIds.Count);
}
}
}
-
No Overhead:
- This approach has no extra layers of abstraction like dictionaries or polymorphism, which makes the code simpler and easier to follow.
-
Extensibility:
- Adding new types still requires minimal effort—just extend the
switch
expression and add a helper method.
- Adding new types still requires minimal effort—just extend the
-
Clarity:
- The logic for each type (
SelectedCa
,SelectedRfactor
) is isolated in small, focused methods.
- The logic for each type (
Let me know if this solution works for you or if you’d like further clarification! 😊