12
\$\begingroup\$

I wanted a function that would return a certificate, given its serial number, regardless of which store it is in. It has to do almost the same exact thing but twice (once for each store). To keep it encapsulated I wrote the "same thing" bit as a lambda, sort of like a Javascript programmer might.

public X509Certificate FindClientCertificate(string serialNumber)
{
    Func<StoreLocation, X509Certificate> find = (location) =>
    {
        X509Store store = new X509Store(location);
        store.Open(OpenFlags.OpenExistingOnly);
        IEnumerable certs = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, true);
        return certs.OfType<X509Certificate>().FirstOrDefault();
    };
    return find(StoreLocation.CurrentUser) ?? find(StoreLocation.LocalMachine);
}

Questions:

  1. Is the purpose and implementation of this function easier to read with the lambda expression? Or is this utterly confusing?

  2. Is there any performance loss or other sort of hidden cost of using a lambda expression as a nested function?

I'm looking forward to C#7 local functions :)

\$\endgroup\$
11
\$\begingroup\$

I agree with @JanDotNet that by using a separate method you will gain readability but that's just my subective view.


As you are asking about C#7 stuff I assume that you will use .NET >= 4.6. Starting with .NET 4.6 the X509Store implements IDisposable hence you should enclose the using of the X509Store inside a using block.

Instead of using a concrete type you could use the var type to declare your variables.

This would result in

public X509Certificate FindClientCertificate(string serialNumber)
{
    return Find(serialNumber, StoreLocation.CurrentUser) ?? Find(serialNumber, StoreLocation.LocalMachine);
}
private static X509Certificate Find(string serialNumber, StoreLocation location)
{
    using (var store = new X509Store(location))
    {
        store.Open(OpenFlags.OpenExistingOnly);
        var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, true);
        return certs.OfType<X509Certificate>().FirstOrDefault();
    }
}
\$\endgroup\$
9
\$\begingroup\$

Is the purpose and implementation of this function easier to read with the lambda expression? Or is this utterly confusing?

IMHO a separate function is more readable:

public X509Certificate FindClientCertificate(string serialNumber)
    => Find(StoreLocation.CurrentUser, serialNumber) 
    ?? Find(StoreLocation.LocalMachine, serialNumber);

private static X509Certificate Find(StoreLocation location, string serialNumber)
{
    X509Store store = new X509Store(location);
    IEnumerable certs = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, true);
    return certs.OfType<X509Certificate>().FirstOrDefault();
}

Local functions make it possible to write succinct code (especially in combination with partial function application as you did), but IMHO it' s harder to read.... probably just a matter of practice ;)


Is there any performance loss or other sort of hidden cost of using a lambda expression as a nested function?

I tested the performance using the following code:

void Main()
{
    var swDelegate = new Stopwatch();
    var swLocal = new Stopwatch();
    var swStatic = new Stopwatch();
    var swDelegateWithoutRecreation = new Stopwatch();

    Func<int, int, int> calcDelegate = (int otherNumber, int number) => number + otherNumber;

    // warm-up
    var a = CalcStatic(10);
    var b = CalcDelegate(10);
    var c = CalcDelegateWithoutRecreation(10, calcDelegate);    
    var d = CalcLocal(10);

    for (int i = 0; i < 10000000; i++)
    {
        swStatic.Start();
        var resStatic = CalcStatic(i);
        swStatic.Stop();
        swDelegateWithoutRecreation.Start();
        var resDelegateWithoutRecreation = CalcDelegateWithoutRecreation(i, calcDelegate);
        swDelegateWithoutRecreation.Stop();
        swDelegate.Start();
        var resDelegate = CalcDelegate(i);
        swDelegate.Stop();
        swLocal.Start();
        var resLocal = CalcLocal(i);
        swLocal.Stop();
    }

    swLocal.ElapsedMilliseconds.Dump("Local");
    swDelegate.ElapsedMilliseconds.Dump("Delegate");
    swDelegateWithoutRecreation.ElapsedMilliseconds.Dump("Delegate (Without Recreation)");
    swStatic.ElapsedMilliseconds.Dump("Static");
}

public int CalcDelegate(int number)
{
    Func<int, int> CalcLocal = (int otherNumber) => number + otherNumber;
    return CalcLocal(1) + CalcLocal(2);
}

public int CalcLocal(int number)
{
    return CalcLocal(1) + CalcLocal(2);

    int CalcLocal(int otherNumber) => number + otherNumber;
}

public int CalcDelegateWithoutRecreation(int number, Func<int, int, int> calcFunc)
{
    return calcFunc(1, number) + calcFunc(2, number);
}

public int CalcStatic(int number) => CalcStatic(number, 1) + CalcStatic(number, 2);
private static int CalcStatic(int number, int otherNumber) => number + otherNumber;

Result (in Milli seconds):

      Local     Delegate    DelegateWR  Static
      266       420         259         249
      265       417         258         247
      270       424         262         249
      272       424         263         251
      269       421         262         248
===============================================
Mean: 268,4     421,2       260,8       248,8
      108 %     169 %       105 %       100 %

Local functions and delegates (without recreation) are almost as fast as the static version while recreating the delegate each time is ~40% slower.

Note that the performance difference is not noticeable for common programming tasks - so don't care about it.

\$\endgroup\$
  • \$\begingroup\$ I guess the additional time comes from recreating the local function on each call but they are ofter convenient to use and optimizations should be made only if the profiler gives a clear indication to do it anyway :-] \$\endgroup\$ – t3chb0t Aug 2 '17 at 7:25
  • \$\begingroup\$ This is what I mean ;-) the local function is probably recreated like any other variable when the outher function gets called. \$\endgroup\$ – t3chb0t Aug 2 '17 at 7:28
  • \$\begingroup\$ Ok, I misunderstood your comment. I'll add another test case without recreating the local functions... \$\endgroup\$ – JanDotNet Aug 2 '17 at 7:30
  • \$\begingroup\$ Oh, one more thing, Func<int, int> CalcLocal = (int otherNumber) => number + otherNumber; this is not a local function... it's a delegate. \$\endgroup\$ – t3chb0t Aug 2 '17 at 7:31
  • 1
    \$\begingroup\$ I've added the IL code for both cases. \$\endgroup\$ – t3chb0t Aug 2 '17 at 7:38
8
\$\begingroup\$

I'm looking forward to C#7 local functions :)

...but they are already there ;-) This means that your lambda can become a local function. I would also put it last and not first. Since it's a method now, the method naming conventions apply, like PascalCasing for its name:

public X509Certificate FindClientCertificate(string serialNumber)
{
    return 
        FindCertificate(StoreLocation.CurrentUser) ?? 
        FindCertificate(StoreLocation.LocalMachine);

    X509Certificate FindCertificate(StoreLocation location)
    {
        X509Store store = new X509Store(location);
        store.Open(OpenFlags.OpenExistingOnly);
        IEnumerable certs = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, true);
        return certs.OfType<X509Certificate>().FirstOrDefault();
    };
}

Is the purpose and implementation of this function easier to read with the lambda expression? Or is this utterly confusing?

Yes and no.

Is there any performance loss or other sort of hidden cost of using a lambda expression as a nested function?

There shouldn't be any but if you are not sure then use a profiler... and you'll identify hundredes of other bottlenecks before this lambda will show in any results.


The IL code generated for a delegate and a local function is also different:

When using a delegate:

FindClientCertificate:
IL_0000:  newobj      UserQuery+<>c__DisplayClass1_0..ctor
IL_0005:  stloc.0     // CS$<>8__locals0
IL_0006:  ldloc.0     // CS$<>8__locals0
IL_0007:  ldarg.1     
IL_0008:  stfld       UserQuery+<>c__DisplayClass1_0.serialNumber
IL_000D:  nop         
IL_000E:  ldloc.0     // CS$<>8__locals0
IL_000F:  ldftn       UserQuery+<>c__DisplayClass1_0.<FindClientCertificate>b__0
IL_0015:  newobj      System.Func<System.Security.Cryptography.X509Certificates.StoreLocation,System.Security.Cryptography.X509Certificates.X509Certificate>..ctor
IL_001A:  stloc.1     // find
IL_001B:  ldloc.1     // find
IL_001C:  ldc.i4.1    
IL_001D:  callvirt    System.Func<System.Security.Cryptography.X509Certificates.StoreLocation,System.Security.Cryptography.X509Certificates.X509Certificate>.Invoke
IL_0022:  dup         
IL_0023:  brtrue.s    IL_002D
IL_0025:  pop         
IL_0026:  ldloc.1     // find
IL_0027:  ldc.i4.2    
IL_0028:  callvirt    System.Func<System.Security.Cryptography.X509Certificates.StoreLocation,System.Security.Cryptography.X509Certificates.X509Certificate>.Invoke
IL_002D:  stloc.2     
IL_002E:  br.s        IL_0030
IL_0030:  ldloc.2     
IL_0031:  ret         

When using a local function:

FindClientCertificate2:
IL_0000:  ldloca.s    00 // CS$<>8__locals0
IL_0002:  initobj     UserQuery.<>c__DisplayClass2_0
IL_0008:  ldloca.s    00 // CS$<>8__locals0
IL_000A:  ldarg.1     
IL_000B:  stfld       UserQuery+<>c__DisplayClass2_0.serialNumber
IL_0010:  nop         
IL_0011:  ldc.i4.1    
IL_0012:  ldloca.s    00 // CS$<>8__locals0
IL_0014:  call        UserQuery.<FindClientCertificate2>g__FindCertificate2_0
IL_0019:  dup         
IL_001A:  brtrue.s    IL_0025
IL_001C:  pop         
IL_001D:  ldc.i4.2    
IL_001E:  ldloca.s    00 // CS$<>8__locals0
IL_0020:  call        UserQuery.<FindClientCertificate2>g__FindCertificate2_0
IL_0025:  stloc.1     
IL_0026:  br.s        IL_0028
IL_0028:  ldloc.1     
IL_0029:  ret         

<FindClientCertificate2>g__FindCertificate2_0:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  newobj      System.Security.Cryptography.X509Certificates.X509Store..ctor
IL_0007:  stloc.0     // store
IL_0008:  ldloc.0     // store
IL_0009:  ldc.i4.4    
IL_000A:  callvirt    System.Security.Cryptography.X509Certificates.X509Store.Open
IL_000F:  nop         
IL_0010:  ldloc.0     // store
IL_0011:  callvirt    System.Security.Cryptography.X509Certificates.X509Store.get_Certificates
IL_0016:  ldc.i4.5    
IL_0017:  ldarg.1     
IL_0018:  ldfld       UserQuery+<>c__DisplayClass2_0.serialNumber
IL_001D:  ldc.i4.1    
IL_001E:  callvirt    System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Find
IL_0023:  stloc.1     // certs
IL_0024:  ldloc.1     // certs
IL_0025:  call        System.Linq.Enumerable.OfType<X509Certificate>
IL_002A:  call        System.Linq.Enumerable.FirstOrDefault<X509Certificate>
IL_002F:  stloc.2     
IL_0030:  br.s        IL_0032
IL_0032:  ldloc.2     
IL_0033:  ret         

<>c__DisplayClass1_0.<FindClientCertificate>b__0:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  newobj      System.Security.Cryptography.X509Certificates.X509Store..ctor
IL_0007:  stloc.0     // store
IL_0008:  ldloc.0     // store
IL_0009:  callvirt    System.Security.Cryptography.X509Certificates.X509Store.get_Certificates
IL_000E:  ldc.i4.5    
IL_000F:  ldarg.0     
IL_0010:  ldfld       UserQuery+<>c__DisplayClass1_0.serialNumber
IL_0015:  ldc.i4.1    
IL_0016:  callvirt    System.Security.Cryptography.X509Certificates.X509Certificate2Collection.Find
IL_001B:  stloc.1     // certs
IL_001C:  ldloc.1     // certs
IL_001D:  call        System.Linq.Enumerable.OfType<X509Certificate>
IL_0022:  call        System.Linq.Enumerable.FirstOrDefault<X509Certificate>
IL_0027:  stloc.2     
IL_0028:  br.s        IL_002A
IL_002A:  ldloc.2     
IL_002B:  ret         

As you can see it is compiled as a member of an anonymous type c__DisplayClass1_0.

\$\endgroup\$
  • \$\begingroup\$ That formatting looks much cleaner! Probably I would prefer that over a separate static function. \$\endgroup\$ – JanDotNet Aug 2 '17 at 7:21
  • \$\begingroup\$ I can't read IL, but it seems, that the anonymous class is created each time the method is called - therefore that (creating one short-living object) is the only overhead when using local functions, isn't it? \$\endgroup\$ – JanDotNet Aug 2 '17 at 7:59
  • \$\begingroup\$ @JanDotNet me either but it looks like the wrapper for a delegate is created on each call whereas the local function is compiled and reused. This would explain the much longer code for it and also the difference in performance. \$\endgroup\$ – t3chb0t Aug 2 '17 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.