2

I'm attempting to rewrite one of my extensions in the new block syntax. It's an extension on IEnumerable<T>:

public static class IEnumerableExtensions
{
    public static double StdDev<T>(this IEnumerable<T> list, Func<T, double?> values)
    {
        // ref: https://stackoverflow.com/questions/2253874/linq-equivalent-for-standard-deviation
        var mean = 0.0;
        var sum = 0.0;
        var stdDev = 0.0;
        var n = 0;

        foreach (var value in list.Select(values))
        {
            if (value is null)
                continue;

            n++;
            var delta = value.Value - mean;
            mean += delta / n;
            sum += delta * (value.Value - mean);
        }

        if (1 < n)
            stdDev = Math.Sqrt(sum / (n - 1));

        return stdDev;
    }
}

I was under the impression it would be as simple as:

public static class IEnumerableExtensions2
{
    extension(IEnumerable<T> list) // Bang! (1)
    {
        public double StdDev<T>(Func<T, double?> values)
        {
            // ref: https://stackoverflow.com/questions/2253874/linq-equivalent-for-standard-deviation
            var mean = 0.0;
            var sum = 0.0;
            var stdDev = 0.0;
            var n = 0;

            foreach (var value in list.Select(values)) // Bang! (2)
            {
                if (value is null)
                    continue;

                n++;
                var delta = value.Value - mean;
                mean += delta / n;
                sum += delta * (value.Value - mean);
            }

            if (1 < n)
                stdDev = Math.Sqrt(sum / (n - 1));

            return stdDev;
        }
    }
}

But I receive the errors:

  1. The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)
  2. 'IEnumerable' does not contain a definition for 'Select' and the best extension method overload 'ImmutableArrayExtensions.Select<T, double?>(ImmutableArray, Func<T, double?>)' requires a receiver of type 'System.Collections.Immutable.ImmutableArray'

I'd like to know how I can include generics in extension blocks.

1
  • 2
    Try extension<T>(IEnumerable<T> list). See extension member blog Commented yesterday

1 Answer 1

6

As noted in the docs:

You need to declare the T generic parameter on the extension itself in order to make it in scope across the whole block

public static class IEnumerableExtensions2
{
    extension<T>(IEnumerable<T> list)
    {
        public double StdDev(Func<T, double?> values)
        {
            // ref: https://stackoverflow.com/questions/2253874/linq-equivalent-for-standard-deviation
            var mean = 0.0;
            var sum = 0.0;
            var n = 0;

            foreach (var value in list.Select(values))
            {
                if (value is null)
                    continue;

                n++;
                var delta = value.Value - mean;
                mean += delta / n;
                sum += delta * (value.Value - mean);
            }

            if (1 < n)
                stdDev = Math.Sqrt(sum / (n - 1));

            return stdDev;
        }
    }
}

On a side note: LINQ aggregations usually use the following logic when there are no items in source:

  • Nullable input: return null
  • Non-nullable: throw InvalidOperationException

So that would end up with

public static class IEnumerableExtensions2
{
    extension<T>(IEnumerable<T> list)
    {
        private (double sum, double n) StdDevImpl(IEnumerable<double> values)
        {
            // ref: https://stackoverflow.com/questions/2253874/linq-equivalent-for-standard-deviation
            var mean = 0.0;
            var sum = 0.0;
            var n = 0;

            foreach (var value in list.Select(values))
            {
                n++;
                var delta = value.Value - mean;
                mean += delta / n;
                sum += delta * (value.Value - mean);
            }

            return (sum, n);
        }

        public double StdDev(Func<T, double> values)
        {
            var (sum, n) = StdDevImpl(values);
            return n switch
            {
                0 => throw new InvalidOperationException("Sequence contains no elements"),
                1 => 0.0,
                _ => Math.Sqrt(sum / (n - 1)),
            };
        }

        public double? StdDev(Func<T, double?> values)
        {
            var (sum, n) = StdDevImpl(values.Where(n => n is not null).Select(n => n.GetValueOrDefault());
            return n switch
            {
                0 => null,
                1 => 0.0,
                _ => Math.Sqrt(sum / (n - 1)),
            };
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! I was skimming some of the getting started documentation but it appears there's not a mention of it yet
Bit of an epic fail of docs, especially given as the example was right there in the whats new in c#14 page
PR merged on adding more information 👍
If were going with standards of implementation of enumerable extensions the generic argument tends to be called TSource

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.