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

public class Test
{
    public static void Fun<T>(List<T> list)
    {
        // The foreach works
        foreach(T i in list)
        {
            Console.WriteLine(i);
        }

        //The following line has error: List<T>' does not contain a definition for 'Sum'
        Console.WriteLine(list.Sum());
    }

     public static void Main()
    {
        var arr = new int[]{1,2,3,4};
        Fun<int>(arr.ToList());
    }
}

the foreach works, but list.Sum() doesn't. It shows:

'List' does not contain a definition for 'Sum' and the best extension method overload 'Queryable.Sum(IQueryable)' requires a receiver of type 'IQueryable'

if I declare as public static void Fun(List<int> list), then it works, anybody know why?

a similar C++ version can work:

#include <iostream>
#include <vector>
using namespace std;

template<class T>
struct MyList
{
    MyList(vector<T> v) : m(v){};
    vector<T> m;
    T Sum()
    {
        T sum = T();
        for(auto i : m)
        {
            sum += i;
        }
        return sum;
    }
};



template<class T>
void Fun(MyList<T> list)
{
    cout << list.Sum() << endl;
}

int main(int argc, char *argv[])
{
    Fun(MyList<int>({1,2,3}));
    Fun(MyList<double>({1.0,2.0,3.0}));
}

output: 
6
6.3
xinglong
  • 115
  • 1
  • 5
  • 1
    [Documentation](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sum?view=netcore-3.1) – ProgrammingLlama Jun 18 '20 at 05:06
  • 1
    `int total = list.Sum(x => Convert.ToInt32(x));` – Vivek Nuna Jun 18 '20 at 05:09
  • 3
    You cant `Sum` a list of *generic* Cat – TheGeneral Jun 18 '20 at 05:09
  • Possibly, duplicate of the next question: https://stackoverflow.com/questions/32664/is-there-a-constraint-that-restricts-my-generic-method-to-numeric-types. – Iliar Turdushev Jun 18 '20 at 05:10
  • @viveknuna Unless OP wants to sum non-integer types (float, double, decimal, etc.). – ProgrammingLlama Jun 18 '20 at 05:17
  • @OlivierRogier Thank you, I could have posted, as it was not confirmed by OP and john has already answered the question with good explanation. – Vivek Nuna Jun 18 '20 at 05:17
  • @viveknuna - `Convert.ToInt32(x)` - No!!! That's an awful way to do it. – Enigmativity Jun 18 '20 at 05:20
  • 2
    Do keep in mind that LINQ already has `Aggregate` which does exactly what you want: `list.Aggregate((x, y) => x + y)`. – Enigmativity Jun 18 '20 at 05:22
  • I want to Sum int,double etc. and also want to Average, I just don't understand why it cannot correctly find the overloaded Sum(IEnumerable) or Sum(IEnumerable) or Average(IEnumerable) or Average(IEnumerable) – xinglong Jun 18 '20 at 05:53
  • Your C++ example works because `MyList` defines `Sum`. `List` in C# does not define `Sum`. `List` implements `IEnumerable` and there are `Sum` [extension methods](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) for specific closed types of `IEnumerable`, as I discussed in my answer. Is there still an outstanding question that you need answered? – ProgrammingLlama Jun 18 '20 at 06:53

2 Answers2

3

The difference between your C++ code and C# code is that in your C++ code, your MyList<T> template explicitly defines a Sum method. In contrast, C#'s List<T> doesn't define a Sum() method. Rather, List<T> implements IEnumerable<T>, and there are extension methods in the System.Linq namespace to provide Sum functionality to certain closed types of IEnumerable<T>.

Referring to the documentation, you'll see that Sum (without a selector, so not something like Sum(x => x.Value)) is only defined for the following closed types:

  • IEnumerable<Single>
  • IEnumerable<Int32>
  • IEnumerable<Int64>
  • IEnumerable<Double>
  • IEnumerable<Decimal>
  • IEnumerable<Nullable<Single>>
  • IEnumerable<Nullable<Int32>>
  • IEnumerable<Nullable<Int64>>
  • IEnumerable<Nullable<Double>>
  • IEnumerable<Nullable<Decimal>>

Therefore, it is not a feature of List<T>, and there is now way for the compiler to verify at compile time that the T in List<T> will be one of these types.

Note that long is the same as Int64, int is the same as Int32, and single is the same as float.

You could possibly provide some kind of aggregation functionality with the aid of a delegate:

public static void Fun<T>(List<T> list, Func<T, T, T> sumDelegate)
{
    T sumValue = default(T);
    // The foreach works
    foreach(T i in list)
    {
        Console.WriteLine(i);
        sumValue = sumDelegate(sumValue, i);
    }

    //The following line has error: List<T>' does not contain a definition for 'Sum'
    Console.WriteLine(sumValue);
}

And then call it like so:

var arr = new int[]{1,2,3,4};
Fun<int>(arr.ToList(), (x, y) => x + y);

Try it online

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • Doesn't the generic like a template in C++? I thought it can deduce the T from "Fun(arr.ToList());", and finally call Sum(IEnumerable) ? – xinglong Jun 18 '20 at 05:39
  • 1
    I'm not too sure about generics in C++, but C# is statically typed, which means the compiler will ensure type safety at compile time. Because the extension methods are defined for specific values of `T`, the compiler has no way of knowing that to do if you were to pass in a `T` of `string`, for example. There is no general `Sum(this IEnumerable source)` method. – ProgrammingLlama Jun 18 '20 at 05:42
  • @xinglong - Since `T` is not constrained then the compiler can only deal with it as if it had the same properties and methods of `object`. – Enigmativity Jun 18 '20 at 05:47
  • 1
    @xinglong - .NET does not compile generics like C++ templates nor like Java does it. The .NET's IL specifically understands generic types so it can create and compile the types at compile-time and doesn't need to make any run-time types - hence it can't do what you think it should. – Enigmativity Jun 18 '20 at 05:50
  • ok, I added a sample C++ version, looks like in .Net do have different behavior from c++. – xinglong Jun 18 '20 at 06:11
  • now I think in .net, "T" looks like an interface, and will only be resolved during runtime, but since the "T" interface doesn't support "+=" operator or Sum extension methods, so we cannot perform the "+=" like C++, in C++, the "T" is the complete type (int, long,double) during compiling, so it knows it suport "+=". The solution should be like @Pavel Anikhouski : Console.WriteLine(((dynamic)list).Sum()); that way the .net runtime will know if the list support Sum() or not. – xinglong Jun 18 '20 at 17:59
0

I don't know, how C++ works in this case, but in your code generic type parameter T isn't constrained to any type and compiler doesn't know whether T supports + operator or not. You can use dynamic keyword here to allow the compiler to determine the type in runtime

public static void Fun<T>(List<T> list)
{
    dynamic sum = default(T);
    foreach (T i in list)
    {
        Console.WriteLine(i);
        sum += (dynamic)i;
    }            

    Console.WriteLine(sum);
}

Or just in one line var sum = list.Aggregate((arg1, arg2) => (dynamic)arg1 + arg2);

But you should be very careful with it. If you pass a type, which doesn't support or overload the +, you'll get an exception in runtime. You can apply generic constraint to T and allow it match only numeric types where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable (it's quite long, but there is no generic constraint to match a numeric types in C# for now)

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
  • that is a good sample, I change the code and it works : Console.WriteLine(((dynamic)list).Sum()); – xinglong Jun 18 '20 at 17:54
  • code like this works: list.Aggregate((sum,x)=>sum+=(dynamic)x) – xinglong Jun 18 '20 at 18:48
  • I don't know, did I click by mistake? I cannot currently upvote. and BTW, I found the (dynamic)list).Sum() cannot work, I think the extension method cannot be dynamically bind to the object, but the list.Aggregate((sum,x)=>sum+=(dynamic)x) works. – xinglong Jun 25 '20 at 05:17