How to return multiple values to a method caller in C# ?

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

MethodBest ForProsCons
out parametersSimple additional returnsNo new types neededCluttered signatures; requires out syntax
Tuples (ValueTuple)Ad-hoc returns; C# 7.0+Lightweight; no custom types; deconstructionNo behavior encapsulation
Custom ClassComplex data; reuse across methodsEncapsulation; can add methods/propertiesOverhead of creating new type
Custom StructSmall data structures; performanceNo heap allocation; better for value semanticsNot suitable for large data
ref parametersModifying existing variablesBidirectional data flowRequires initialization; complex usage
CollectionsDynamic number of return valuesFlexible sizeBoxing; no named properties; type safety issues

Key Recommendations

  1. C# 7.0+ Projects: Use ValueTuples with named elements for most cases
  2. Structured Data: Use custom classes when:
  • Data has business meaning
  • You need to attach behavior (methods)
  • Reusing the type across multiple methods
  1. Performance-Critical Paths: Consider struct for small datasets
  2. Interoperability: Use out for APIs following patterns like TryParse:
   if (int.TryParse("123", out int result)) { ... }
  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *