Showing posts with label extensions. Show all posts
Showing posts with label extensions. Show all posts

Thursday, December 9, 2021

Linq DistinctBy

I've had to search for this quite a few times so I figured it was probably time to write it up. I actually did find this answer somewhere else (here, if you're interested) and modified it only slightly to follow my standards.

As I often (somehow) forget, the default .Distinct() extension in System.Linq for an IEnumerable just checks to see if objects are the exact same as each other. You may recall in C# that being the exact same means the two objects are actually references to the same exact object. I can say with a good amount of confidence that in 16+ years of working in C# that's never actually been what I'm trying to do when I use .Distinct(). I'd say my most common usage is determining whether two objects have the same data on them - usually one specific field (like an ID field, for example). There are Stack Overflow posts and extension libraries out there that include this, but I'm pretty loathe to bring in a whole library for one simple extension method. Which brings us to today.

I'm using Dapper to get a list of objects from the database and then using the splitOn feature to get the children of those objects. Think of a teacher and students: I'm getting all the teachers in the school and then all of each teacher's students in a single query, then I want to break the students out according to their teachers by using the splitOn feature of Dapper. That's no problem and doesn't even require me to use .Distinct(). If I also include the clubs that each teacher oversees, I could easily end up with duplicate students in my results. The easiest way to get the distinct students in my results would be to use the .Distinct() extension method included in System.Linq, if only that worked the way it seems like it should. Instead, I'll have to write my own. So here we are.


   1: public static IEnumerable<T> DistinctBy<T>(this IEnumerable<T> list, Func<T, object> propertySelector) where T : class
   2: {
   3:   return list.GroupBy(propertySelector).Select(x => x.First());
   4: }

That's it, really. The somewhat obvious flaw is that we'll take the first match we find, but if you're looking for distinct objects, that really shouldn't be too big of a deal. Hopefully this helps you (even if "you" are really just future me).

Thursday, September 12, 2019

Converting Strings to Enums

In my last post I showed how to use the DescriptionAttribute to specify a customized value for an Enum value and I promised to show how to convert back to an Enum. Here it is, the FromDescription string extension method. This time I did remember to get the link from the SO answer I got this from. Thanks, max!
   1: public static T FromDescription(this string description)
   2: {
   3:   var type = typeof(T;
   4:   if (!type.IsEnum)
   5:   {
   6:     throw new InvalidOperationException($"{description} does not match any of the enumeration values);
   7:   }
   8: 
   9:   foreach (var field in type.GetFields())
  10:   {
  11:     if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
  12:     {
  13:       if (attribute.Description == description)
  14:       {
  15:         return (T)field.GetValue(null);
  16:       }
  17:     }
  18:     else
  19:     {
  20:       if (field.Name == description)
  21:       {
  22:         return (T)field.GetValue(null);
  23:       }
  24:     }
  25:   }
  26: 
  27:   throw new InvalidOperationException($"{description} does not match any of the enumeration values);
  28: }

Now that we have the extension method we can invoke it on any string, like this:
var contactType = "Phone Call".FromDescription<ContactTypes>();

Wednesday, September 11, 2019

Converting Enums to Strings

Yes, I'm aware you can easily get the string value of an Enum by using .ToString(). And that's great in many circumstances. I recently came across one where it was important to us to be able to translate an Enum to a custom string that included spaces (which you can't do with an Enum's value). I should have bookmarked the SO answer where I got this code from, but I didn't (sorry, Original Author, whoever you are!).

This extension method takes advantage of the built-in DescriptionAttribute. We decorate our Enum values with the DescriptionAttribute and provide whatever we want, like this:
   1: public enum ContactTypes
   2: {
   3:   [Description("Phone Call")]
   4:   Phone,
   5:   Email,
   6:   Chat
   7: }

Once we've decorated the Enum values, we'll need the extension method to get the description we just provided.
   1: public static string ToDescription(this Enum value)
   2: {
   3:   var da = (DescriptionAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
   4: 
   5:   return da.Length > 0 ? da[0].Description : value.ToString();
   6: }

Now we just need to use the extension method on our Enum to get the description we specified. Let's say we have a class that looks like this:
   1: public class Interaction
   2: {
   3:   public string UserName { get; set; }
   4: 
   5:   public ContactTypes ContactType { get; set; }
   6: }

If we wanted to get "Phone Call", "Email", and "Chat" returned (depending on which one was assigned in the class), we'd use the ToDescription extension method like this:
   1: var contact = new Interaction { ContactType = ContactTypes.Phone, UserName = "engineer-andrew" };
   2: var contactType = contact.ContactType.ToDescription();

Since we didn't use the DescriptionAttribute on Chat and Email, they'll default to just use .ToString() so they'd return "Chat" and "Email", respectively.
That's all there is to it. I know it's kinda simple, but I've used it a couple of times and my rule is to blog about those things. In the next post I'll show how to go the other way (take a string and find its matching Enum value).