In C#, you can return multiple values from a method using several approaches. Below are the most common techniques, explained in detail with examples:
1. Using out
Parameters
The out
keyword allows you to return values through parameters. Values must be assigned in the method.
Example:
public class OutExample
{
public void GetDimensions(int area, out int width, out int height)
{
width = area / 10;
height = 10;
}
public static void Main()
{
GetDimensions(100, out int w, out int h);
Console.WriteLine($"Width: {w}, Height: {h}");
}
}
Output:Width: 10, Height: 10
Note:
- Caller must use
out
when invoking the method - Parameters are uninitialized before the call
2. Using Tuples
Tuples provide a lightweight way to return multiple values without custom classes.
a) ValueTuple (C# 7.0+) – Preferred
public class TupleExample
{
public (string, int, bool) GetUserInfo()
{
return ("Alice", 30, true); // Tuple literal
}
public static void Main()
{
var user = GetUserInfo();
Console.WriteLine($"Name: {user.Item1}, Age: {user.Item2}, Active: {user.Item3}");
}
}
b) Named Tuple Elements (More Readable)
public (string Name, int Age, bool IsActive) GetUserInfo()
{
return (Name: "Bob", Age: 25, IsActive: false);
}
public static void Main()
{
var user = GetUserInfo();
Console.WriteLine($"Name: {user.Name}, Age: {user.Age}"); // Access by name
}
c) Deconstruction
Unpack tuple directly into variables:
var (name, age, isActive) = GetUserInfo();
Console.WriteLine($"{name} is {age} years old");
3. Using Custom Class/Struct
Create a dedicated type to encapsulate multiple return values.
a) Class (Reference Type)
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsActive { get; set; }
}
public UserInfo GetUserInfo()
{
return new UserInfo { Name = "Charlie", Age = 28, IsActive = true };
}
b) Struct (Value Type – Use for small data)
public struct Dimensions
{
public int Width;
public int Height;
}
public Dimensions GetDimensions()
{
return new Dimensions { Width = 1920, Height = 1080 };
}
4. Using ref
Parameters
Similar to out
, but variables must be initialized before passing.
Example:
public void Calculate(ref int sum, ref int product, int a, int b)
{
sum = a + b;
product = a * b;
}
public static void Main()
{
int s = 0, p = 0;
Calculate(ref s, ref p, 5, 4);
Console.WriteLine($"Sum: {s}, Product: {p}"); // Sum: 9, Product: 20
}
5. Using Collections/Arrays
Return multiple values in a collection type.
Example with Array:
public int[] GetNumbers()
{
return new int[] { 1, 2, 3 };
}
Example with Dictionary:
public Dictionary<string, object> GetUser()
{
return new Dictionary<string, object>
{
["Name"] = "Diana",
["Age"] = 32,
["IsAdmin"] = true
};
}
Comparison of Methods
Method | Best For | Pros | Cons |
---|---|---|---|
out parameters | Simple additional returns | No new types needed | Cluttered signatures; requires out syntax |
Tuples (ValueTuple) | Ad-hoc returns; C# 7.0+ | Lightweight; no custom types; deconstruction | No behavior encapsulation |
Custom Class | Complex data; reuse across methods | Encapsulation; can add methods/properties | Overhead of creating new type |
Custom Struct | Small data structures; performance | No heap allocation; better for value semantics | Not suitable for large data |
ref parameters | Modifying existing variables | Bidirectional data flow | Requires initialization; complex usage |
Collections | Dynamic number of return values | Flexible size | Boxing; no named properties; type safety issues |
Key Recommendations
- C# 7.0+ Projects: Use ValueTuples with named elements for most cases
- Structured Data: Use custom classes when:
- Data has business meaning
- You need to attach behavior (methods)
- Reusing the type across multiple methods
- Performance-Critical Paths: Consider
struct
for small datasets - Interoperability: Use
out
for APIs following patterns likeTryParse
:
if (int.TryParse("123", out int result)) { ... }
- Avoid:
ref
for returns (except special scenarios), collections for fixed-value returns
Complete ValueTuple Example
public class Program
{
public static (double sum, double product) Calculate(double x, double y)
{
return (x + y, x * y);
}
public static void Main()
{
// Direct access
var result = Calculate(3, 4);
Console.WriteLine($"Sum: {result.sum}, Product: {result.product}");
// Deconstruction
(var s, var p) = Calculate(5, 6);
Console.WriteLine($"Sum: {s}, Product: {p}");
// Discard one value
(var sumOnly, _) = Calculate(7, 8);
Console.WriteLine($"Sum: {sumOnly}");
}
}
Output:
Sum: 7, Product: 12
Sum: 11, Product: 30
Sum: 15
Choose the method that best fits your use case based on readability, maintainability, and performance requirements.