[C#] Code Sharing via Generic Method Definitions and Type Inference

In C#, when you want to apply the “same logic” to different data types, defining a Generic Method is the most efficient approach.

Creating overload methods for every specific type, such as int, double, or DateTime, leads to code duplication and reduces maintainability. By defining generic methods using type parameters (<T>), you can implement reusable logic while maintaining type safety.

In this article, we will explain how to define generic methods and use type inference, using an implementation of a general-purpose method that searches for and returns the “maximum value” from an arbitrary data collection as an example.

目次

Overview of Generic Methods

A generic method is defined by specifying a type parameter, such as <T>, immediately after the method name. When calling the method, you can either explicitly specify the concrete type or let the compiler infer the type from the arguments.

Furthermore, when performing operations that require “value comparison,” such as finding a maximum value, it is essential to apply Type Constraints using the where clause to guarantee that the arbitrary type T is comparable.

Practical Code Example: Maximum Value Search Utility

The following code is an example implementation of a method that extracts the maximum value from any array capable of magnitude comparison, such as a list of numbers or dates.

using System;
using System.Collections.Generic;
using System.Linq;

namespace GenericUtilities
{
    class Program
    {
        static void Main()
        {
            // Scenario:
            // We want to get the "maximum value" from arrays of different data types.
            // 1. Temperature data (double type)
            // 2. Access log timestamps (DateTime type)

            var temperatures = new[] { 25.5, 30.2, 28.4, 31.0, 26.8 };
            
            var accessLogs = new[]
            {
                new DateTime(2025, 5, 1, 10, 0, 0),
                new DateTime(2025, 5, 1, 15, 30, 0),
                new DateTime(2025, 5, 1, 09, 15, 0)
            };

            // 1. Calling by explicitly specifying the type argument
            // By specifying <double>, T is treated as double.
            double maxTemp = DataAnalyzer.FindMax<double>(temperatures);
            Console.WriteLine($"Max Temperature: {maxTemp}");

            // 2. Calling using Type Inference
            // Since the compiler infers T as DateTime from the argument type (DateTime[]),
            // the <DateTime> description can be omitted.
            DateTime lastAccess = DataAnalyzer.FindMax(accessLogs);
            Console.WriteLine($"Last Access: {lastAccess}");
        }
    }

    public static class DataAnalyzer
    {
        /// <summary>
        /// Searches for and returns the maximum value in a sequence.
        /// </summary>
        /// <typeparam name="T">Comparable type (must implement IComparable&lt;T&gt;)</typeparam>
        /// <param name="sequence">Collection to search</param>
        /// <returns>The maximum value</returns>
        public static T FindMax<T>(IEnumerable<T> sequence)
            where T : IComparable<T> // Type Constraint: T must be comparable
        {
            if (sequence == null || !sequence.Any())
            {
                throw new ArgumentException("Sequence is empty or null.");
            }

            // Assume the first element is the temporary maximum
            T max = sequence.First();

            // Compare with the second and subsequent elements
            foreach (var item in sequence.Skip(1))
            {
                // If CompareTo returns a value greater than 0, item is larger
                if (item.CompareTo(max) > 0)
                {
                    max = item;
                }
            }

            return max;
        }
    }
}

Execution Result

Max Temperature: 31
Last Access: 2025/05/01 15:30:00

Technical Points

1. Importance of Type Constraints (where clause)

When performing comparison operations like item.CompareTo(max) within a generic method, there must be a guarantee that type T possesses a comparison method.

If simply defined as <T>, T is treated as object, so comparison operators or the CompareTo method cannot be used. By writing where T : IComparable<T>, a constraint is applied stating “T is limited to types that implement the IComparable<T> interface,” allowing safe description of comparison logic within the method.

2. Type Inference

This is the feature that allows omitting <T> at the time of the call, as in FindMax(accessLogs). The compiler detects that the argument accessLogs is IEnumerable<DateTime> and automatically determines T to be DateTime. This reduces the amount of code written and improves readability.

3. Improved Reusability

This FindMax<T> method can be used for any type that implements IComparable<T>, such as int, long, decimal, string, TimeSpan, etc. When creating libraries of logic that do not depend on specific types, generic methods are an essential technology.

Summary

By leveraging generic methods, you can eliminate code duplication caused by type differences and build versatile, robust logic. Especially when implementing collection operations or comparison/conversion processing, consider whether a generic design that does not depend on specific types is possible.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次