Generic Methods
One method with its own type parameter: how the compiler infers it from the arguments, and when you must write it out.
static T Pick<T>(T[] items) // called as Pick(items) or as Pick<int>(items)A generic method is a single method with its own blank for a type. The blank goes in angle brackets after the method name, as in static T MiddleOf<T>(T[] items). That one method then works for an array of int, an array of string, or an array of anything else.
A generic method is independent of the type that contains it. The containing type does not have to be generic, and a generic class can hold ordinary methods beside generic ones.
Inference
Most calls never mention the type argument. The compiler reads it from the arguments you pass, which is called type inference. MiddleOf(numbers) and MiddleOf<int>(numbers) compile to the same call. The first is shorthand for the second, and both are worth recognising in other people's code.
Inference looks only at the arguments. It does not look at the return type and it does not look at constraints. So a method with no parameters, such as MakeList<T>(), can never have its type argument inferred, and neither can a method whose type parameter appears only in the return type. Write the type argument out in those cases.
What trips people up
Either every type argument is written or none is. There is no way to supply one and let the compiler work out the other.
Inference also runs before overload resolution. The compiler infers for every method of that name first, then chooses among only the candidates where inference succeeded. A failed inference therefore removes a candidate quietly, and the error you see can name a different overload than the one you meant.
Example
int[] numbers = [10, 20, 30]; // collection expression
string[] letters = new string[] { "a", "b", "c" }; // the longhand form
// The compiler reads T from the argument. The angle brackets stay empty.
Console.WriteLine(MiddleOf(numbers)); // T is inferred as int
Console.WriteLine(MiddleOf(letters)); // T is inferred as string
// Writing T out means exactly the same thing.
Console.WriteLine(MiddleOf<int>(numbers));
// This method takes no arguments, so there is nothing to infer from.
// T has to be supplied by hand or the call will not compile.
List<string> empty = MakeList<string>();
Console.WriteLine(empty.Count);
static T MiddleOf<T>(T[] items)
{
// items.Length / 2 is integer division, so 3 / 2 is 1.
return items[items.Length / 2];
}
static List<T> MakeList<T>()
{
return new List<T>();
}
// Output:
// 20
// b
// 20
// 0When to use it
- Write one Swap or Clamp that serves every type instead of one copy per type.
- Add a helper that reads a configuration value and hands it back already typed.
- Give a typed repository class a method whose type differs from what the class stores.
- Write a factory method whose type argument the caller chooses at the call site.
More examples
Two overloads, and the optional parameter that replaces them
Two methods sharing a name are overloads, and overloads must live in a type, so these sit in a static class rather than at the top of the file. The last comment shows the shorthand: a single method with a default value for the second parameter does the same job. Prefer the optional parameter in new code, and keep the overload pair when you must add a parameter without recompiling existing callers.
string[] words = ["pear", "apple", "plum"];
// The short call reaches the two-argument method through the one-argument one.
Console.WriteLine(Text.Join(words));
Console.WriteLine(Text.Join(words, " | "));
static class Text
{
// The short form exists only to supply a default separator.
public static string Join<T>(T[] items) => Join(items, ", ");
public static string Join<T>(T[] items, string separator)
{
return string.Join(separator, items);
}
// One method with an optional parameter is the shorthand for the pair
// above, and callers cannot tell the difference:
// public static string Join<T>(T[] items, string separator = ", ")
}
// Output:
// pear, apple, plum
// pear | apple | plumA generic method that modifies its arguments
Swap is the method generics were invented for: the logic has nothing to do with the type, so writing it once per type is waste. A ref parameter needs an exact type match, so a ref object parameter would not accept a string variable at all. The generic version sidesteps that, because T becomes string for the whole call.
int x = 1;
int y = 2;
Swap(ref x, ref y); // T inferred as int
Console.WriteLine($"{x} {y}");
string first = "a";
string second = "b";
Swap<string>(ref first, ref second); // the same method, T written out
Console.WriteLine($"{first} {second}");
static void Swap<T>(ref T left, ref T right)
{
T temporary = left;
left = right;
right = temporary;
}
// Output:
// 2 1
// b aA generic method inside a generic class
A method inside a generic class can use the class's type parameter freely, and it can also declare its own. Give the method's parameter a different name: reusing the class's name compiles but produces warning CS0693, and the two then mean different types in the same body. This pattern appears whenever a typed container needs one method that is not tied to what it contains.
// The class has its own type parameter, and the method adds another.
Repository<string> repo = new Repository<string>("Ada");
Console.WriteLine(repo.PairWith(36));
Console.WriteLine(repo.PairWith("Lovelace"));
class Repository<TItem>
{
private readonly TItem _item;
public Repository(TItem item)
{
_item = item;
}
// TOther belongs to the method. It is inferred from the argument,
// independently of TItem. Naming it TItem as well would warn CS0693,
// because the inner name would hide the class's one.
public string PairWith<TOther>(TOther other)
{
return $"{_item}/{other}";
}
}
// Output:
// Ada/36
// Ada/Lovelace
Discussion