C
C#5mo ago
Saiyanslayer

✅ Using Params with Pattern Matching Switch statement

I have the following enums:
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}
I want to link a modification to a query based on which enum you select by switch statement pattern matching:
public static class FilterExtensions {
public static IQueryable Filter(this IQueryable query, params SearchFilters[] filters) => filters switch {
[] => query,

_ => query,
};
}
public static class FilterExtensions {
public static IQueryable Filter(this IQueryable query, params SearchFilters[] filters) => filters switch {
[] => query,

_ => query,
};
}
I can't find a syntax that works with the params keyword. I could do a foreach, but I'm tryign to improve my knowledge. Is there a way to parse a switch pattern matching using an array of inputs?
12 Replies
Saiyanslayer
SaiyanslayerOP5mo ago
I think this would work:
var array when array.Contains(SearchFilters.None) => query,
var array when array.Contains(SearchFilters.None) => query,
nvm, figured it's betetr to do it like this:
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}

public IQueryable<PatientModel> Search(this IQueryable<PatientModel> query, SearchFilters filter)
=> filter switch {
SearchFilters.None => query,
SearchFilters.Active => query.Where(p => !p.IsArchived),
SearchFilters.Overdue => query.Where(p => p.WhenStarted < DateTime.UtcNow),
SearchFilters.Archived => query.Where(p => p.IsArchived),
_ => query,
};
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}

public IQueryable<PatientModel> Search(this IQueryable<PatientModel> query, SearchFilters filter)
=> filter switch {
SearchFilters.None => query,
SearchFilters.Active => query.Where(p => !p.IsArchived),
SearchFilters.Overdue => query.Where(p => p.WhenStarted < DateTime.UtcNow),
SearchFilters.Archived => query.Where(p => p.IsArchived),
_ => query,
};
and another function can handle if it's an array had too many complications. IQueryable needs a generic to search with, so I guess I'll have to make this for each entity that needs to be filtered here's the final result:
public static class FilterExtensions {
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}

public static Expression<Func<PatientModel, bool>> GetFilterExpression(this SearchFilters filter) => filter switch {
SearchFilters.None => p => true,
SearchFilters.Active => p => !p.IsArchived,
SearchFilters.Overdue => p => p.WhenStarted < DateTime.UtcNow,
SearchFilters.Archived => p => p.IsArchived,
_ => x => true,
};

public static IQueryable<PatientModel> ApplyFilters(this IQueryable<PatientModel> query, SearchFilters[] filters) {
foreach(var filter in filters) {
query = query.Where(filter.GetFilterExpression());
}

return query;
}

}
public static class FilterExtensions {
public enum SearchFilters {
None,
Active,
Overdue,
Archived
}

public static Expression<Func<PatientModel, bool>> GetFilterExpression(this SearchFilters filter) => filter switch {
SearchFilters.None => p => true,
SearchFilters.Active => p => !p.IsArchived,
SearchFilters.Overdue => p => p.WhenStarted < DateTime.UtcNow,
SearchFilters.Archived => p => p.IsArchived,
_ => x => true,
};

public static IQueryable<PatientModel> ApplyFilters(this IQueryable<PatientModel> query, SearchFilters[] filters) {
foreach(var filter in filters) {
query = query.Where(filter.GetFilterExpression());
}

return query;
}

}
SleepWellPupper
SleepWellPupper5mo ago
Sounds like you want a flags enum
SleepWellPupper
SleepWellPupper5mo ago
They allow you to pass a set of SearchFilter values into a single non-array variable of that type. And you can check for each flag value via switch expression. It's overlaying bitmasks.
Saiyanslayer
SaiyanslayerOP5mo ago
That's a great suggestion, but I'm worried about handling conflicting filters (IsArchived true, false, or ignore for example) Nvm I have that issue anyways
Anton
Anton5mo ago
that's actually easy with bit opeartions it's also a good idea to wrap these in a struct and do helper bool properties and wrap the bit operations in methods with more intuitive names flags need a source generator to be great I probably should write one, I have to deal with this stuff way too often
Saiyanslayer
SaiyanslayerOP5mo ago
Do you have an example of what you mean by source generator?
SleepWellPupper
SleepWellPupper5mo ago
Just use flag enums, no need to overcomplicate it.
Saiyanslayer
SaiyanslayerOP5mo ago
True, but I'm trying to overcomplicate ti a bit to learn more. Then I'll know what helps and what's just fluff
SleepWellPupper
SleepWellPupper5mo ago
Well writing an sg to get flags enum and essentially the same functionality would certainly overcomplicate it. #roslyn has some pinned messages to get you started on that.
Anton
Anton5mo ago
yeah learn bit operations first bit operations is something most people have no knowledge of in my experience so you could consider it an advanced topic in that sense
Saiyanslayer
SaiyanslayerOP5mo ago
No worries on bit operations. I've got a handle on them, just never had a practical use for them until now. And holy hell, you're not kidding about source generators, I see what you mean now. This is perfect, thanks! Decided against source generators for now. Not enough benefit for the effort put into it. I want to try it in the future though.
Here's the results:
public enum PatientSort {
[Description("Newest")]Newest,
[Description("Name: A-Z")] NameAsc,
[Description("Name: Z-A")] NameDesc,
[Description("Due: Newest First")] DueNewest,
[Description("Due: Oldest First")] DueOldest
}


/// <summary>
/// Filters that apply to the PatientModel
/// uses enum Flags
/// ref: https://gaevoy.com/2023/11/14/learn-enum-flags-csharp.html
/// </summary>
public static class FilterExtensions {
[Flags]
public enum SearchFilters {
[Description("All")] None = 0,
[Description("Active")] IsActive = 1, // 0b0001
[Description("Overdue")] IsOverdue = 1 << 1, // 0b0010
[Description("Archived")] IsArchived = 1 << 2, // 0b0100
}

public static Expression<Func<PatientModel, bool>> GetFilterExpression(this SearchFilters filter) => filter switch {
var flag when flag == SearchFilters.None => p => true,
var flag when flag.HasFlag(SearchFilters.IsActive)
&& !flag.HasFlag(SearchFilters.IsArchived) => p => !p.IsArchived,
var flag when flag.HasFlag(SearchFilters.IsOverdue) => p => p.WhenStarted < DateTime.UtcNow,
var flag when flag.HasFlag(SearchFilters.IsArchived)
&& !flag.HasFlag(SearchFilters.IsActive) => p => p.IsArchived,
_ => x => true,
};

public static IQueryable<PatientModel> ApplyFilters(this IQueryable<PatientModel> query, SearchFilters filter) {
if (filter == SearchFilters.None) { return query; }

foreach(var flag in filter.GetFlags()) {
if (flag == SearchFilters.None) { continue; }
query = query.Where(flag.GetFilterExpression());
}

return query;
}

}
public enum PatientSort {
[Description("Newest")]Newest,
[Description("Name: A-Z")] NameAsc,
[Description("Name: Z-A")] NameDesc,
[Description("Due: Newest First")] DueNewest,
[Description("Due: Oldest First")] DueOldest
}


/// <summary>
/// Filters that apply to the PatientModel
/// uses enum Flags
/// ref: https://gaevoy.com/2023/11/14/learn-enum-flags-csharp.html
/// </summary>
public static class FilterExtensions {
[Flags]
public enum SearchFilters {
[Description("All")] None = 0,
[Description("Active")] IsActive = 1, // 0b0001
[Description("Overdue")] IsOverdue = 1 << 1, // 0b0010
[Description("Archived")] IsArchived = 1 << 2, // 0b0100
}

public static Expression<Func<PatientModel, bool>> GetFilterExpression(this SearchFilters filter) => filter switch {
var flag when flag == SearchFilters.None => p => true,
var flag when flag.HasFlag(SearchFilters.IsActive)
&& !flag.HasFlag(SearchFilters.IsArchived) => p => !p.IsArchived,
var flag when flag.HasFlag(SearchFilters.IsOverdue) => p => p.WhenStarted < DateTime.UtcNow,
var flag when flag.HasFlag(SearchFilters.IsArchived)
&& !flag.HasFlag(SearchFilters.IsActive) => p => p.IsArchived,
_ => x => true,
};

public static IQueryable<PatientModel> ApplyFilters(this IQueryable<PatientModel> query, SearchFilters filter) {
if (filter == SearchFilters.None) { return query; }

foreach(var flag in filter.GetFlags()) {
if (flag == SearchFilters.None) { continue; }
query = query.Where(flag.GetFilterExpression());
}

return query;
}

}
Here are the extensions:
/// <summary>
/// https://gaevoy.com/2023/11/14/learn-enum-flags-csharp.html
/// </summary>
public static class EnumFlagExtensions {
public static IEnumerable<T> GetFlags<T>(this T value) where T : struct, Enum {
return Enum.GetValues<T>().Where(e => value.HasFlag(e)).ToArray();
}

/// <summary>
/// Toggles the flag
/// </summary>
/// <typeparam name="TEnum"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static TEnum SetFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, !value.ContainsFlag(flag));

/// <summary>
/// Sets the flag to a specific value
/// </summary>
/// <typeparam name="TEnum"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <param name="state"></param>
/// <returns></returns>
public static TEnum SetFlag<TEnum>(this TEnum value, TEnum flag, bool state) where TEnum : Enum {
var left = Caster<TEnum, UInt64>.Cast(value);
var right = Caster<TEnum, UInt64>.Cast(flag);
var result = state
? left | right
: left & ~right;
return Caster<ulong, TEnum>.Cast(result);
}

public static TEnum RaiseFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, true);

public static TEnum LowerFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, false);

public static bool ContainsFlag<TEnum>(this TEnum value, TEnum other) where TEnum: Enum
=> (Caster<TEnum, UInt64>.Cast(value) & Caster<TEnum, UInt64>.Cast(other)) != 0;



}
/// <summary>
/// https://gaevoy.com/2023/11/14/learn-enum-flags-csharp.html
/// </summary>
public static class EnumFlagExtensions {
public static IEnumerable<T> GetFlags<T>(this T value) where T : struct, Enum {
return Enum.GetValues<T>().Where(e => value.HasFlag(e)).ToArray();
}

/// <summary>
/// Toggles the flag
/// </summary>
/// <typeparam name="TEnum"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static TEnum SetFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, !value.ContainsFlag(flag));

/// <summary>
/// Sets the flag to a specific value
/// </summary>
/// <typeparam name="TEnum"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <param name="state"></param>
/// <returns></returns>
public static TEnum SetFlag<TEnum>(this TEnum value, TEnum flag, bool state) where TEnum : Enum {
var left = Caster<TEnum, UInt64>.Cast(value);
var right = Caster<TEnum, UInt64>.Cast(flag);
var result = state
? left | right
: left & ~right;
return Caster<ulong, TEnum>.Cast(result);
}

public static TEnum RaiseFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, true);

public static TEnum LowerFlag<TEnum>(this TEnum value, TEnum flag) where TEnum : Enum
=> value.SetFlag(flag, false);

public static bool ContainsFlag<TEnum>(this TEnum value, TEnum other) where TEnum: Enum
=> (Caster<TEnum, UInt64>.Cast(value) & Caster<TEnum, UInt64>.Cast(other)) != 0;



}
/// <summary>
/// To Cast Enums without boxing to reduce garbage collection
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
private static class Caster<TSource, TTarget> {
public static readonly Func<TSource, TTarget> Cast = CreateConvertMethod();

private static Func<TSource, TTarget> CreateConvertMethod() {
var p = Expression.Parameter(typeof(TSource));
var c = Expression.ConvertChecked(p, typeof(TTarget));
return Expression.Lambda<Func<TSource, TTarget>>(c, p).Compile();
}
}
/// <summary>
/// To Cast Enums without boxing to reduce garbage collection
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
private static class Caster<TSource, TTarget> {
public static readonly Func<TSource, TTarget> Cast = CreateConvertMethod();

private static Func<TSource, TTarget> CreateConvertMethod() {
var p = Expression.Parameter(typeof(TSource));
var c = Expression.ConvertChecked(p, typeof(TTarget));
return Expression.Lambda<Func<TSource, TTarget>>(c, p).Compile();
}
}
public static class EnumExtensions {

/// <summary>
/// Returns the Description attribute of the Enum or the Enum's name
/// </summary>
/// <param name="value"></param>
/// <returns>Description attribute to string, or Enum's name</returns>
public static string GetDescription(this Enum value) {
var attribute = value
.GetType()
.GetField(value.ToString())
?.GetCustomAttributes(typeof(DescriptionAttribute), false)
.SingleOrDefault() as DescriptionAttribute;

return attribute is null
? value.ToString()
: attribute.Description;
}

/// <summary>
/// Returns a list of all enums of the Enum type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>List of Enum belonging to the type</returns>
public static List<T> GetEnums<T>() where T : Enum
=> Enum.GetValues(typeof(T)).Cast<T>().ToList();

/// <summary>
/// Returns an array of names for each Enum belonging to the enum type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string[] GetEnumDescriptions<T>() where T : Enum
=> GetEnums<T>()
.Select(x => x.GetDescription())
.ToArray();
}
public static class EnumExtensions {

/// <summary>
/// Returns the Description attribute of the Enum or the Enum's name
/// </summary>
/// <param name="value"></param>
/// <returns>Description attribute to string, or Enum's name</returns>
public static string GetDescription(this Enum value) {
var attribute = value
.GetType()
.GetField(value.ToString())
?.GetCustomAttributes(typeof(DescriptionAttribute), false)
.SingleOrDefault() as DescriptionAttribute;

return attribute is null
? value.ToString()
: attribute.Description;
}

/// <summary>
/// Returns a list of all enums of the Enum type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>List of Enum belonging to the type</returns>
public static List<T> GetEnums<T>() where T : Enum
=> Enum.GetValues(typeof(T)).Cast<T>().ToList();

/// <summary>
/// Returns an array of names for each Enum belonging to the enum type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string[] GetEnumDescriptions<T>() where T : Enum
=> GetEnums<T>()
.Select(x => x.GetDescription())
.ToArray();
}
just incase someone else could learn from this
Want results from more Discord servers?
Add your server