13

I'd really like a a generic numeric type as the second generic type parameter of the Func<TInput, THere, TResult> given below so I can provide either an integer or a double or a decimal as and when I like.

var _resultSelectors = new Dictionary<string, Func<DateTime, /*here*/ double, DateTime>>();

// so I can do
_resultSelector.Add("foo", (DateTime dt, int x) => ...);
_resultSelector.Add("bar", (DateTime dt, double d) => ...);
_resultSelector.Add("gar", (DateTime dt, float f) => ...);
_resultSelector.Add("har", (DateTime dt, decimal d) => ...);

Short of:

  1. making my own type; or
  2. using object and boxing a value type and unboxing it; or
  3. Using the dynamic keyword,

Is there a proper way to do that in C#?

I guess Java has a Number class that probably fits in my requirements but I was wondering if C# has anything similar.

I guess there isn't any such thing in C# but I thought I'll ask to be sure.

Water Cooler v2
  • 32,724
  • 54
  • 166
  • 336
  • If you might have all 4 I'd suggest your pick one of `double` or `decimal` (depending on your use case, i.e. is floating point appropriate) and convert the rest to that. – Charles Mager Jun 29 '16 at 12:31
  • @CharlesMager That's a good idea. – Water Cooler v2 Jun 29 '16 at 12:32
  • There's no generic interface but you would still have the problem of finding the correct funcs to invoke based on the runtime type of the value. Where do the concrete numeric values come from? – Lee Jun 29 '16 at 12:34
  • I'm *very* tempted to mark this as a duplicate of [this question](http://stackoverflow.com/questions/828807/what-is-the-base-class-for-c-sharp-numeric-value-types). – krillgar Jun 29 '16 at 12:35
  • @DStanley You're right. A generic `INumeric` would be a rabbit hole that ran deep infinitely. But in the meantime, what could be wrong in having a non-generic marker interface like simply `INumeric`? – Water Cooler v2 Jun 29 '16 at 12:42
  • @WaterCoolerv2 Not a thing - but it wouldn't solve your problem. You'd still have to declare the lambda as a `Func` – D Stanley Jun 29 '16 at 12:49

3 Answers3

13

No, there is not. Generics and arithmetic operations (+, -, *, /, etc.) simply do not work together. This is an issue that is brought up many times and the C# design comitee has never addressed (to be fair, this feature would need work on the CLR too, as pointed out by Eric Lippert in answer linked further on).

Curisously, if you inspect the source code of the .NET Framework you'll see that in some stage of development there was an IArithmetic<T> interface, but it was scrapped; see here.

You can read more about it, in this SO answer.

Community
  • 1
  • 1
InBetween
  • 32,319
  • 3
  • 50
  • 90
5

Even if there were a base type or interface that encompasses numeric types, because you've wrapped the constraint in a Func it wouldn't do you any good. the input types for Func are contravariant, so its parameters can't me more derived than what's declared.

In other words, you can't replace a Func<DateTime, ValueType, DateTime> with a Func<DateTime, int, DateTime> like you can with IEnumerable<T> (you can replace an IEnumerable<ValueType> with an IEnumerable<int>)

I think your best bets are dynamic (still type safe, but at run-time versus compile-time) or double or decimal if you want to do math and don't have to stay in the same type.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • `Func` has variance. Its input parameters are contravariant and its output is covariant. Here is the definition of the particular overload I use: public `delegate TResult Func(T1 arg1, T2 arg2);` – Water Cooler v2 Jun 29 '16 at 13:02
  • Oh sorry I re-read your message again. You're right. Because of the contravariance of its input types, the actual closed generic parameters cannot be more derived than their compile time types. – Water Cooler v2 Jun 29 '16 at 13:03
1

C# 10 and .NET 6: Solution from November 2021 onwards

Great news: there is now a solution for this in .NET 6 and C# 10, cf. https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

When you want any number, you could use INumber<T>. In case you need any specific operator, use the corresponding interfaces e.g. IAdditionOperators for a + b or IIncrementOperators for a++ etc.

Note: At the time of my answer, this feature was only a preview. Microsoft will keep this for the final version of .NET 6 as they still want to allow breaking chances. To use the feature, preview features must be enabled in the project configuration:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
    <LangVersion>preview</LangVersion>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
  </ItemGroup>

</Project>
SommerEngineering
  • 1,412
  • 1
  • 20
  • 26