0

Abstract

The case: Application contains a lot of views with a list of data. Data views (lists of records) have pagination, filtering and sorting options. The user must be able to select a "sorting property" to sort the shown data by a specific property. The user-interface contains a selection control (ComboBox) the user can interact with. The combobox options represent the sorting properties that are currently available in the displayed data. It's a very standard/common case.

Example image:

enter image description here

The image shows a typical datalist view. The bar above the data grid shows the ComboBox. I wanted to create a class structure to design a general pagination solution. One part of my code is as follows:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

enum SortProperty {
    Id, Name, Date, Balance
}
    

public class Program
{
    public static void Main()
    {
        Program main = new Program();
        main.Test();
    }
    
    private void Test() {
        
        ObservableCollection<SortPropertyOption<SortProperty>> sortingOptions = new ObservableCollection<SortPropertyOption<SortProperty>>();
        
        // The outside world can create options and use its own data type (class generic type) to represent the
        // property in the data. In this case an enum is used by the outside world.
        // but it could also be a string (maybe a database field name) or an integer, etc...
        sortingOptions.Add(new SortPropertyOption<SortProperty>(SortProperty.Name, "Name"));
        sortingOptions.Add(new SortPropertyOption<SortProperty>(SortProperty.Date, "Date"));
        sortingOptions.Add(new SortPropertyOption<SortProperty>(SortProperty.Balance, "Balance"));
        
        // SortPropertyOption collection could be passed to the view (by a binding or something).
        // When an option is selected in the view, the SortPropertyOption.OptionValue can be accessed by the using-code (outside world)
        // which then knows what to do.
    }
}

public class SortPropertyOption<T> {
    
    private T _optionValue;
    private string _text;
    
    public T OptionValue { get { return _optionValue; } }
    public string Text { get { return _text; } }
    
    public SortPropertyOption(T optionValue, string text) {
        
        _optionValue = optionValue;
        _text = text;
    }
}

The reason for the generic in SortPropertyOption is that the outside world that uses my classes, can use its own data type for identifying the property/column in the dataset. The datalayer can provide an enum or strings that say what properties are available in the data.

In my question, an enum is used as generic type for SortPropertyOption. Is that acceptable?

Also, a .NET Fiddle link: https://dotnetfiddle.net/vyVWaf

3
  • 2
    If you can compile it without any error this is acceptable by the compiler. Who else should define if its acceptable or not?
    – Mr Zach
    Commented May 31, 2021 at 19:01
  • I was wondering if this would be a code smell or bad practice in the context of software engineering. Commented May 31, 2021 at 19:23
  • Personally I've found the Enums.NET library along with attributes (e.g. [DisplayName] or [Description]) to be a useful way of populating GUI elements from enums. Commented Jun 1, 2021 at 8:20

1 Answer 1

6

On a technical level, there's nothing wrong with using an enum as the type used in a generic type.

However, I've noticed that in most cases where this is being suggested, there's a conflation between data (i.e. values and class instances) and structure (i.e. types, classes, ...). The two are very much related, but they don't mix and match.
Types are a design-time focus, data is a runtime focus. When using generic typing, you need to be thinking about using different types, not using different values of the same type.

You've fallen into the same trap here.

public class SortPropertyOption<T> 
{
    private T _optionValue;
    private string _text;

    public T OptionValue { get { return _optionValue; } }
    public string Text { get { return _text; } }

    public SortPropertyOption(T optionValue, string text) {
    
        _optionValue = optionValue;
        _text = text;
    }
}

A generic class definition effectively states "I don't yet know what this T will be, but here's how I'm going to use it". But you named your class (SortPropertyOption) after the enum type that T will be (SortProperty).

This conceptually defeats the purpose of having a generic type to begin with. You could've just hardcoded the type and it would've worked:

public class SortPropertyOption
{
    private SortProperty _optionValue;
    private string _text;

    public SortProperty OptionValue { get { return _optionValue; } }
    public string Text { get { return _text; } }

    public SortPropertyOption(SortProperty optionValue, string text) {
    
        _optionValue = optionValue;
        _text = text;
    }
}

When you change this, you'll notice that your code keeps working, which proves the point that you're not leveraging the generic typing in your class.

However, if this generic class were used for many different kinds of types, as a way to track a typed in-memory value to a dropdownlist-friendly key/value pair (note: you're only using a display value and not a separate key value, which is also fine), that would a leverage the generic type in a way that it actually makes sense. Rather than change your old code, I'm only going to rename it:

public class DropdownListOption<T> 
{
    private T _optionValue;
    private string _text;

    public T OptionValue { get { return _optionValue; } }
    public string Text { get { return _text; } }

    public DropdownListOption(T optionValue, string text) {
    
        _optionValue = optionValue;
        _text = text;
    }
}

I only renamed the class. On a technical level, nothing changed. But this new name reveals that this now could have many more (generic) implementations.

  • ObservableCollection<SortPropertyOption<int>> for a dropdownlist to select how many items per page to load
  • ObservableCollection<SortPropertyOption<TimeSpan>> for a dropdownlist to filter the list based on age of the entry (e.g. "from the last 5 minutes")
  • ObservableCollection<SortPropertyOption<Person>> for a dropdownlist to filter on wro created the entry (e.g. "only created by Bob")
  • ...

Now you're thinking with generics.

So, was your old code bad? On a technical level, no. However, the naming you used suggested that your intention didn't quite need the generic typing to begin with. Either that, or you already intended to use different generic types but your class was badly named. I can't judge that.

1
  • 1
    +1 Very good answer. The second case you mention is what I intended to create. Commented Jun 1, 2021 at 19:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.