1

I have an interesting question for you all.

public Func<T, int> GetLambdaFor<T>()
{
   // ....
}

public void SetLambdaForAnyType(Func<?, int> lambda)
{
   // ....
}

In the code sample above I would like to find out if it is at all possible to specify a substitutable Generic lambda expression in C# using the SET method while retrieving a specifically typed Generic lambda expression using the GET method.

So let me give an example (ofcourse it features broken C#, I would like to find out if it is possible and if so, how to do it):

MyClass.SetLambdaForAnyType<?>((a) => new SomeValueGenerator<?>(a).GetValue());
int generateValue = MyClass.GetLambdaFor<string>()("blah");
generateValue = MyClass.GetLambdaFor<DateTime>()(DateTime.Now);
// etc...

I hope it makes more sense now. Any ideas? If it is not possible, any alternative possibilities?

Dandré
  • 2,053
  • 3
  • 18
  • 38
  • how could you get a string and return an `int` ? or get a datetime and return an int ? what is the criteria? do you exactly know what is `Func` delegate doing ? you are returning `Func` delegate from your `GetLambdaFor` function but you are trying to assign it to an **int** variable – Selman Genç Jan 27 '14 at 21:20
  • @Selman22 He knows the argument for the delegate based on the generic argument provided to `GetLambdaFor`. He's invoking it with a value before assigning the result to a variable, so that part is all Kosher too. – Servy Jan 27 '14 at 21:21
  • @Selman22: his `GetLambdaFor` always returns a function that returns `int`. – Wiktor Zychla Jan 27 '14 at 21:22

2 Answers2

4

A lambda expression cannot introduce generic parameters, there is no syntax for it. You will have to use generic methods instead of lambdas, and use reflection to construct closed generic methods for particular types, like this (code not tested):

public Func<T, int> GetLambdaFor<T> ()
{
    return GetLambdaFor<T> (m_method, m_target) ;
}

public void SetLambdaForAnyType (Func<object, int> lambda)
{
   if (lambda == null) throw new ArgumentNullException ("lambda") ;

   // delegate complicated signature checks to CLR
   try
   {
       var definition = lambda.Method.GetGenericMethodDefinition () ;
       GetLambdaFor<Dummy> (definition, lambda.Target) ;
       m_method = definition ;
       m_target = lambda.Target ;
   }
   catch (Exception e)
   {
       throw new ArgumentException ("lambda", e) ;
   }
}

private sealed class Dummy {}
private MethodInfo m_method ;
private object     m_target ;
static Func<T, int> GetLambdaFor<T> (MethodInfo method, object target)
{
    return (Func<T, int>) Delegate.CreateDelegate (typeof (Func<T, int>),
        target, method.MakeGenericMethod (typeof (T))) ;
}

Then you use it like this

// in your class
private static int GetValueFromGenerator<T> (T a)
{
    return new SomeValueGenerator<T> (a).GetValue () ;
}

MyClass.SetLambdaForAnyType (GetValueFromGenerator) ;

Finally, if your 'lambdas' will always be of this form, you might want to introduce an interface for GetValue and instead of creating generic methods on-the-fly, create generic types for these generators, activate them and cast to IGetValue.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • I had a strong suspicion that I needed to make things more loosely coupled, it was just that I wasn't sure how to deal with lambda expressions with generics though. But your answer seems legit enough to make me excited to try it. Great stuff! – Dandré Jan 28 '14 at 05:38
  • I copied your code and made some fixes to it but it works! I can definitely work with this, thanks a lot! Will use it in my actual code base - http://renewalprojects.codeplex.com/wikipage?title=EasyProxy%20library&referringTitle=Documentation – Dandré Jan 28 '14 at 11:01
  • @Dandré Did you edit the post here to include your code fixes? – srm Jun 09 '17 at 18:25
  • I haven't changed anything here. I can't remember if there were issues or not but you can find my implementation here: http://renewalprojects.codeplex.com/SourceControl/latest#Source/CommonLibraries/RenewalProjects.Libraries.Reflection/LambdaTemplate.cs – Dandré Jun 11 '17 at 13:01
3

You can do it, sure. You lose type safety within this type, but we know it'll work correctly anyway from an external point of view:

public class Foo
{
    private Dictionary<Type, Delegate> dictionary =
        new Dictionary<Type, Delegate>();

    public Func<T, int> GetLambdaFor<T>()
    {
        return dictionary[typeof(T)] as Func<T, int>;
    }
    public void SetLambdaFor<T>(Func<T, int> func)
    {
        dictionary[typeof(T)] = func;
    }
}

Of course, you may want to use TryGetValue from the dictionary, if you want to handle the case that there is no entry for that type.

Another option would be to just use a thread static field:

public class Foo2<T>
{
    [ThreadStatic]
    public Func<T, int> Generator { get; set; }
}

Then it's as simple as:

Foo2<string>.Generator = s => 5;
int result = Foo2<string>.Generator("asdf");
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Doesn't it require invoking `SetLambdaFor` for all possible `T`s separately? I understand that he would like to have the very same function that is set once to act differently then according to `T`. – Wiktor Zychla Jan 27 '14 at 21:24
  • I agree with Wiktor. I need to "universally" specify a lambda template to be used for various types, not for each individual type. – Dandré Jan 28 '14 at 05:41
  • While not a lambda, you can use a static class (or an interface) with a generic static method. Code here: https://stackoverflow.com/a/45311276/6655648 – Pablo H Jul 25 '17 at 18:46
  • @PabloH The question is already using static methods, clearly it's already understood that they exist. My answer also uses generic methods in an attempt to implement the behavior asked for (which you don't provide a solution for). – Servy Jul 25 '17 at 19:20