Exploring Pattern Matching in C# - Bill Wagner

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
good morning so I'm bill Wagner we're going to be looking at pattern matching in c-sharp 7:00 this morning so most important things in this slide is how to reach me via email or Twitter if you have any questions after this talk go ahead and reach out there I will post both the slides and the demos on my github repository later I will announce it on the Twitter handle okay so we're going to talk a little bit about the languages and where C sharp is going and C sharp seven primarily the investments are evolving for modern patterns distributed applications so today commence C sharp first came out as a language roughly 2000 we wrote desktop applications maybe client server maybe websites mostly static pages going down and forms it's very different than the program for writing today we're writing small services that run on distributed machines that leads to a different decomposition of problems objects aren't necessarily the right way to solve every problem we're more often separating data for transit from the algorithms that operate on that data that's probably the big theme of c-sharp 7 is things like tuples pattern matching and others for anyone who's in Kathleen's talk so you have a good overview of all of those now one of the things that we really liked as working with the c-sharp team is c-sharp is one of the most popular technologies out there according to the Stack Overflow survey toward the end of 2016 and the set gets a little smaller if we look at the most loved technologies c-sharp is one of a very small set of languages on both of those lists so it's used by a really really large number of developers and by and large the developers that use c-sharp like it so it's an important goal for us is to keep that and hopefully move up on both of those f-sharp of course has a little more love which is nice they're good people okay so let's look at some of the big big things as when going on in c-sharp instead of running on Windows we now run everywhere just to show that we do still have support for Windows today's demos are on windows I talked Wednesday was all on a Mac so we do still support Windows you know that net is the system component more and more with dotnet core you could deploy dotnet with your application it's going to make side by side a little bit easier as we get a little bit more investments there it's going to make it easier for things like running and docker containers and so on can deploy something that's totally self-contained running out of VM and the CLR we're now also making investments into compiling c-sharp through dotnet native into native code on various platforms so you'll be actually running native code that's started to see sure the compilers have open ap is the Roselyn API if you're here last year I gave a talk on building rosin Roselyn analyzers there's been further investments in that so you can continue to do open compiler api's and add your own hooks edit in your favorite editor or Visual Studio once again some of the demos I did Wednesday weren't vs code today I'm going to use Visual Studio proprietary everything is open source so as I discussed pattern matching too at the end of the talk I'll show you how to find this spec for patterns and investments that will go on beyond c-sharp 7 as we do more things with patterns and you can engage in the discussion there directly on github on the Rosalind repo okay so let's look at pattern matching as a definition of what it means for c-sharp developers in terms of what you've already done with code and the kinds of things that we're going to do the definition I want to use for pattern matching is that we are going to test that a value or variable has a certain shape and extract information when it does so there's three keys to that definition in terms of what we're going to learn about our matching first we test something about a variable not necessarily its type not necessarily its structure in terms of a normative way c-sharp types classes have names and so on but we're going to test something about a variable has a certain shape certain properties certain values in those properties and then we are going to extract information when it does so once we found what we're looking for we're going to do something with that variable now we already do that but the syntax is a little bit clumsy for instance this is one of the some of the code that we're going to start with if you look this is the kind of code that we would have to write today to test what type of an object comes in there's a lot of if this is some type and so on and then we move down and then we'll do a cast because we figured out what type it is so those are separate operations we're going to perform some tests and then we're going to if that test succeeds then we've got a block of code that does some work on that variable it's a little clumsy this is structured to be somewhat clean my last two demos I'm going to try to do pull request for a couple live projects to change from code that's noticeably uglier than this into using c-sharp seven pattern matching so that's going to be your key here's what we're going to do is we're going to take this test wow that we leave is in the wrong spot there well and test the value and then bring it back and extract at the same time in c-sharp seven we're taking some small steps toward pattern matching there is a rich vocabulary for patterns if we look at the spec you'll see a lot of them laid out c-sharp has three particular ones that are supported that are new first of all there's the consonant pattern which to some degree is already supported you can test if a value has a certain variable has a certain value we're comparing against the constant well now we can compare and extract at the same time we have a tight pattern we are going to test that a variable is a certain type and we'll extract into a new variable that type immediately in one expression and then there is the null pattern which is kind of a special case of the constant pattern but it's interesting because null doesn't necessarily have a type so we're going to support look and look at those three supported patterns and there are two statements that work with that there's been enhancements to the is statement and there have been enhancements to the switch statement let's support these patterns okay so what we're going to get is we're gonna get is expressions to test if a variable is a certain type and the if so we'll extract information from it switchin case expressions they can test the type and extract information from it and the switch is further enhanced with a wind conditional so we can test the type and if the type matches we can apply further tests that look at information and properties on that particular type so let's start exploring a little bit so here's our first our main demo I built a console application that declares several different variables with some interesting or non interesting values so I've got a couple integers so we know that's a struct actually has values has numbers I've got a nullable out first one is the null value or the default value for a nullable int I have a couple different numbers there and then we have strength and put a few interesting cases there I've got null as a string I have the empty string I have just a whitespace string and then just a phrase and then I've built the structure that's a point structure up here simple Cartesian points x and y override the two strings with prints and things out and then I've built a few interesting points one that has at the origin 0 0 3 4 nice little Pythagorean triangle and 23 and 19 which are two prime numbers that add up to 42 because that's how we roll okay and then I've just built an array of objects that has each one of those in and we're going to walk through and convert both this ugly code for is patterns into using pattern matching and then we'll do the same thing with the switch expressions if I run it right now I'm going to pass in turn each one of these different variables into these tests below that say all right is this an integer is this nullable int is it a string and is it a point and if so then I'm going to cast it and then print out a little information about it so if we run it we'll find okay so we've started our is expressions I found and it to integers 0 and 42 interestingly I found two more integers 0 and 42 found strings notice that there are three and I found three points okay that's all good so a few interesting things we learned right away from this code is that it never finds the null values okay if we look through this it never finds the null string or the nullable null int because null will fall through both of those is tests notice it doesn't find the null string either because it is on a null string well it's null it's not really a string is it so those tests are already failing and the final interesting one is notice that even though I'm testing for a nullable int I never wrote out that we found a nullable int everything we found actually already did the cast and cast it to an integer so we never actually found that tight so because of the rules of how convergence happened this code is a little bit interesting and already has some subtle subtle issues with it depending on what we were trying to do and finally if we look at the code itself we can see a couple interesting little bits of ugliness here if you look at where I'm testing for an integer or a nullable int where I'm converting it to an int and what I'm testing for those that point which is a structure I have to use a cast rather than the as operator right because we can't do is with value types until now okay so let's start to rewrite some of this and use pattern expressions and what we're going to find is codes going to get a little shorter it's going to get a little cleaner and it's not going to make as many mistakes so first we want to test and see if it's an end so what I'm going to do is I'm just going to say if my object to test is an int I so I'll do the assignment right there on the is statement I notice that now it is okay I can check to see if it's an nth I'm going to do a assignment life is good and I'm going to do that just copy that right line right up here all good so that one so far is pretty go great okay so we've got rid of about two or three lines there let's now we're going to do the same thing here of my nullable int that should work and I will leave this that's not make that one so now what's interesting with nullable ends is that first one that object to test if I check to see if it is an integer and assign it that will work with nullable n that has a value because I can convert that integer to can convert d nullable into an INT directly so let's just go ahead and run right now and now we'll see as we get pretty much the same result as we had before except that I'm getting builders I miss my ski oh yes ah right so let's change that one to J down there there we go okay cool alright so we still find convert our knowable instance cool now what was interesting is that the only thing that's really a ting check here that we find is if that nullable int actually is null okay so let's check that null pattern so if object to test is null and then of course we're not going to try to do this cast because that would clearly fail okay and now you see that we find the nulls both for the knowable int when it has its default value and we find the nulls for the strength is the first one of the strings now points out that we find a null all right so far so good now let's start to expand and really look at the string and say that you know there's a few different things that we can do here that's really fun so we found a string we found a point well now let's really start to play and add new other interesting things that we want to test for let's say I want to test for an actual value so up here I'm going to say if object to test is int we had a 5 up there if I remember right oh no we are 42 which we can't do with those let's start adding those in the switch so now to expand this let's look at our switch pattern which gives us quite a bit more interesting things that we can do so we're used to a switch in general and that we can put a few things under the constant pattern well now what we can do is we're going to be able to switch on any type so I'm switching on object so I'm going to build case labels based on what we find in that object and here's similar to the is statement that we had up above I'm going to say if our case int I and we'll just do the right line yeah okay so so far we just added that one particular case I could run through and now you can see as we start on the switch expressions we're starting to find some things with different integers okay well values are still important so let's now say I have case int I when I is 42 and I deleted my opening brace there accidentally all right cool clothes just run let's make sure I type the code correctly yes found the secret got our zeroes and final secret so now what's cool here in the new syntax note that first of all inside the switch statement each case block now introduces its own scope for variables I've got two variables I there and they are two different variables for the first switch and for the second okay so we have that particular feature where now each one of those blocks in the newer switch statement is its own scope even though we don't have braces to kind of imply it's kind of cool this also starts to show one of the interesting things that now happens with case and switch is that now order matters you can see I'm now getting red squiggles here because it's telling me you know what you're never going to get to that code because every integer was found in the case of boss so we now have unreachable code detected because scale Able's will be evaluated in the order that they were they are they appear in the switch statement with one exception I can write my default here and of course I still need a break by convention we'll probably right that particular case statement at the bottom but the compiler plays a little bit of magic and I can put it in whatever order I wanted to be and it will still get evaluated last okay don't do that that was really ugly code all right so we've got that pattern set up so far awesome all right so now so far we've checked for integers okay well we could do a case do our null case and now suddenly we're going to pick up the nullable int that has no value the string that has nothing in it and then we're still not parsing our strings in our points yet okay let's start looking at strengths so we'll start with just the case string s and now we're going to write line that we found the string okay again only a few things that can help us out we've now found three different strains that are not null first one is the empty string second one is a bunch of whitespace all right well let's add a string s when a string that is null or empty and you see we solve the empty string we still have our whitespace string in there okay well it's easy enough to cope with we'll add another one immediately following and say you know instead of null or empty let's just do nollie whitespace which I think this is another whitespace and again now we get to pull those in and we get things that have different statements in it all right so now we're finding whether or not we have an empty string we're finding just blanks we're doing those tests and we're putting that code together and it's noticeably cleaner than it was before both up here and down here and then we can add our case for the points and we'll find the origin okay so we can do any kinds of tests than the object that we find we can call any expression in their test any value test any properties do whatever we want in a one Clause okay all good stuff so effectively what we've done here again as we look at how this code looks let's just evaluate a little bit about the structure of the code and the kinds of things that we're doing we've taken this test to see if the value has something extract information from it and then do something with it we've combined the first two test to see if a value has certain properties extract it into a new value and then do whatever we want with that particular value okay we've expanded the case statement so that now case can do to work on any different kinds of variable not simply numerix or the string it cannot work on any type and in addition to just checking the type we can check particular values of a type I think I can also simplify this and just say they're not in this bill thought that was there okay so we have a simpler syntax to do the test and to put this into our different work together all right so far so good so now let's walk through in a larger project where we see some real-life code as to what kind of ugliness happens in current more imperative tests and let's apply c-sharp pattern matching to those so I'm going to open up no to time and I wish John were here in this talk this would be fine and the give a little bit of context to this particular code what this method does other times a big project since why we don't use big projects for demos I get to wait okay what this particular method does is is going to try to get a noted information for a particular culture to figure out what time zone you're in what culture you're working with and so on and if we walk through we can see that it does is okay so if the provider that came in was null well we're going to use the current culture okay so far so good if the provider is not null well let's see if it is a culture info object if so I'm going to call get format info culture info object get the right information for the culture that we're working in at this point in time which is cash - no - time because you're probably using the same time zones for different different tests and then if it is not a current culture info or a culture info object it's probably a date-time format info in which case we're going to create a noted time format info with the invariant culture pass that back on and that's what we use for our time info and if none of those work what we want to throw a new argument exception because we really don't know what's going on and you probably are using no - time wrong because it can't be John Skeets bug we know that okay so let's change this to C sharp 7 well I've got about three or four different tests here then I'm going to check for so let's just do a switch on provider and the first one he has is that null and if we get the case no I'm just going to return get format info from culture info current culture okay so I'm combining a little bit of stuff just to make this a bit cleaner and that if you look what will happen in the original code down below if provider is no I'm going to sign it to current culture current culture is a culture info object so the next test is going to succeed and then I'm going to drop down into that return get current info okay so my first practice is you know now I've got to read about six or seven lines of code to make that conversion as to what actually happens in the case when you pass a null provider it's nice clean code but I still have to do a little bit of analysis to figure out exactly what's right that new line I just wrote is really really clear you pass in null you get the timing flow for the current culture groovy next one let's now just say is culture info and we'll assign to the same variable name because it's a nice way to do it and we're still going to return get format info and now we're going to call that culture info that connects the variable it was just assigned hate to make a bug on chain on stage because I want to submit this PR and then the last one I'm going to grab these two comments because I think that's probably important and then we're going to have a date-time format info and here I'm going to grab that return statement and I thought I'd grab the extra no yeah I did grab the extra one there there we go I'm getting the red squigglies because I've left the code below that read eclairs those same variables and now I can delete this code and if I didn't match any of the cases then we're just going to throw that argument exception so taking that 15 20 lines of code that we had to snake through just a little bit and clean that up just a just a touch nice well so that's a 1 real-world example of working with pattern matching to do a few different type tests and extract that information in a more clean fashion so let's look at the second one if anyone was in damien and david's talk on monday they talked about building tag helpers and they talked about building tag helpers yeah so I'm telling you're going to fail out me on this one alright fine I had this happen a few times because I've got to build it slightly newer than they have give me this switch to their branch and let's close that and reopen it and now we'll migrate that again yes cool and as this loads what happens with the tag helpers you get this tag and the information that you need may be stored in a few different ways in a few different places inside the tag helper and Damian talked about the particular code there starts right here at line 45 where we're going to look at this source which is fugitive the particular attribute for the source we're going to try to look at it as a string if that's null then we're going to walk through and look at this as an HTML string that may or may be and if so we're going to grab its value and if that path is null still then we're going to do some other code yeah this looks kind of ugly and you know it sneaking through a few different cases there to unwind if the path is null we're going to start doing some stuff we're going to try to see if it's an HTML context if that HTML context your content has a value if it finally if it isn't an HTML string and if some different content we're going to have to create a string writer and write the path into another variable do some other bits and then we're going to sneak through and make this other source value to string and then eventually we're going to use it down here this just feels buggy all right so let's let's work on this all right we're going to do some massive pair programming here because I'm probably going to need some help who's written tag helpers anybody I'm on my own awesome all right so let's first let's start and say ok so it looks like I want to really want to switch not on path but on source dot value all right and if source dot value is an HTML string right that's that first kind of test that's up there so we're going to do a case HTML string let's give it a name and it looks like we really care whether or not that value is null or not so let's just let's just be careful here because it there's this little bit here where I talked with Damien and David yesterday as I was working up this demo where if you look very carefully this is where like pattern matching as a syntax if you look carefully past that sources HTML string okay and then I'm using the Elvis operator so if it's null this thing is going to return null but there's another path where it is an HTML string and the value for it is null which Damien said that's evil it should never happen but I don't like to introduce bugs so let's just make sure that doesn't happen so it could be possible so I'm just going to say it's HTML string I'm going to say when HTML string dot value does not equal no because then I just feel a little bit better about this case and if that happens well then I'm just going to say that path equals HTML string dot value and that's that's good that takes care of that part cool well now otherwise it looks like what I need to check and see is if I have an eye HTML content and it looks like that's path HTML content was B that they used and now I need to do this bit of code where I'm going to say and it looks like I'm testing null there too so let's actually and I don't need to add that test okay because that source that value as I HTML content that will return null if it's not an HTML content right and we already saw that with a pattern match it's not going to match if it's null so I'm not going to get a null case in here so okay I'm in good shape there so now I can just copy this code so I don't really want to change it and that now goes up under here we can all set path that's all good okay what else could it be so it got an HTML string we've got a path that HTML content well looks like the other path that we might have is we're going to say so I'll value that to string okay sounds good I already kind of did that up there unless it is a string so if past is null so if value is a string looks like we had that up here before so we should say case string s and we're just going to say path equals s and then we need a break and now here we're just going to say this is default string so I feel a little bit better about the structure of that code so it could be a string it could be an HTML string it could be an HTML content it could be some other things so then let's go our default and here I'm going to say path equals source value question mark dot to strength so it could still be null if you know really truly evil evil things happen and that's the case that kind of falls through here at the bottom I'm going to add the question mark that just in case okay so now I remove close to 20 lines of code there and I've replaced it with all close to 20 lines ago but it's so much cooler now in seriousness when I look at this code once I get used to the fact that there's some new syntax here I find that this the structure of it is relatively easy and simple okay and as I was walking through doing this demo and this is you know after practicing and analyzing the code and doing it and thinking about it I feel in converting it I had to understand exactly all the paths through those multiple if-else what could be what value statements here there are four distinct cases all of which are clear all of which are easier to read so it's one of the things that we want to do when we're adding new syntax into the c-sharp language is the developer that comes after this or damien when he reviews my pull request should be able to look at and analyze this code and say yeah this looks pretty good I think I like the way this is set up I understand what it does you may say nasty things we'll see but it it looks looks noticeably cleaner we have some really good stuff that comes in there okay so do I make those pull requests I'm actually not going to make them on stage but I will do them later this afternoon and the reason is I'm working on a build on this machine right now that has slightly newer stuff that kind of breaks the project system a little bit so I can't build either one of these projects completely on this machine and I'd like to actually build them in and in the case of no to time run all the tests before I submit that PR but it should be done later today so two new bits of real-world examples of how in both cases what we think is if I look at the changes now now I'm not going to up there that's what you saw but if I look at the changes I made both here and in no two time we took code that required a fair amount of analysis and made it noticeably simpler it's easier to analyze and understand all right so let's finish up with a few more ideas of where some things might go and some of the conclusions that we have so what we really want to do here what really is going on with pattern matching and c-sharp 7 right now is taking a very tentative steps into this world pattern matching expressions enable a concise tests and extract syntax so in all cases we're testing something and extracting information from it and then we can with confidence we can work with that information knowing that things are set and it's based on an object's shape as you read some of the specs one of the terms that you'll hear c-sharp has a normative type system it's based on names typically all of our classes have names our structs have names and for two objects to be the same type they have to have the same class name or struct name as we get into this idea of patterns and tuples and stuff we're doing what's called structural typing two objects have the same type if their shape is the same and we'll define shape to say they have the same properties and that same property types same property names okay so that's really what we're doing we're moving a little bit more away from objects a little bit a little bit could think it's dynamic but it really isn't it's all it's all static typing I know it's the back of this code just a bit and discuss a bit about some of the syntax rules so go back to the small demo and some of the last rules that are that play into pattern matching syntax once again c-sharp trying to be a pit of success language it's hard to write code that doesn't work the scope of these variables when you extract when you test and extract should be the scope inside that block underneath it the one exception to that is an if statement because of how a couple things work which is that if I were to make this modification here the scope of F right here is valid anywhere in this else clause so that means it is in scope from here to here okay now the scope of AI is anywhere in the method okay and that's kind of because to be an if-statement it has to be in the enclosing block so for any any pattern match variable its scope is the block that's enclosing it is however we also have definite assignment rules so if I try to use let's say this variable I if I come down here and I try to do a right line of I okay it's in scope but I will get a compiler error that says it's a use of an unassigned local variable so it hasn't been assigned the only time that I is assigned it is definitely assigned underneath this if so by combining this test and the extraction one of the rules that is very important is you can't miss use those variables in a place where they didn't get assigned when the test didn't actually pass okay other ones just for convenience because it's just plain time if you heard John on Wednesday is the case blocks introduce new scopes in c-sharp seven in terms of scoping variables so there is you can squint kind of and see curly braces around those case blocks so that that is a distinct different I in those first two cases okay so what's going to happen in the future with different pattern matching well the best place to find out is if you go to your favorite searching engine and if you search for Roslyn pattern matching and github on any reasonable search engine this page will be the first page you find it's the Rosalind repo blob features pattern slash dot slash features slash which is why I said search for github Roslyn and patterns I'm talk about the is expression talked about our type pattern constant pattern we already know about we're testing is the value some of these are the ones that aren't in the build right now they're still being talked at and worked on the VAR pattern more or less implicitly is there var will match anything it returns the same type of what was there so in my expressions here if I were to do case var oh so I don't think and then down here was an object so you can see if I press the dot it's kind of small like it equals get hash code get type and two strings so this particular case matches anything the VAR pattern it will match whatever is on the right hand side and it will return the same thing type the same way wildcard and positional patterns are under discussion under work and then we will have the ability to override operator is is also in here you will be able to define an operator ears that can take an object and return a different type of object so you can write your own patterns if you will the rest of these are pretty similar and finally we will have I think Katherine went through this somewhat is destructuring assignment which i think is more covered under tuples but if you want to get involved in these discussions we can you can participate here for any of the things that are still working finally as we finish up and as I take questions a effective c-sharp the third edition is available now because it's available now it covers c-sharp six I'm working on a second edition of more effective c-sharp which will cover the features in c-sharp seven I'm going to close with a bit of work on what's going on in the dotnet foundation where the manage language compilers are part of that foundation there are a few bits of dotnet that are now open-source in public I like the fact that it keeps taking me longer and longer to work on this particular slide as I walk through and watch what happens more and more things are coming out vs test came out and was open sourced I think yesterday was when that announcement was so there are more different areas that you are open sourced so in general what do we think about in terms of open source investments if you look at the dotnet framework everything is very involving asp.net is if you look at dotnet core well all of it if you look at xamarin all of it if you look at the infrastructure underneath each one of those compilers language runtime components gotten its standard in many ways will be open sourced fully supported cross-platform Windows Linux OS X visual students support and so on these are up-to-date I think we've had even more now which is really awesome and finally a couple few links on areas that you can work through in terms of getting more information about dinette and so on and make the survey get swag and finally just how serious we are about open source even our swag repos open source if you have ideas go ahead and submit a PR so we have a bit of time left I think the demos went just a little quicker than I was guessing are there questions on anything related to pattern matching in c-sharp yeah how is it compiled that how is the VAR case at compile time okay let's just put it right here okay so how it is it is a compiler compile-time what normally happens with VAR if I assign var to some object by the types of on the right hand side of that assignment right so if I look at the right hand side of this assignment what that particular pattern is saying is I want to take that object to test and I want to assign it to a new variable obj what is the type of object to test the static type it's object so it would be the type of obj object right so the VAR pattern is in part therefore syntactic niceness and in part there because there are patterns where it is reasonably useful if I want to do something in the default case I can assign a variable and do some work with it okay so var does return the VAR pattern will return a new label to the same object so if it is a value type it will return a new value type with the same value right but based on the static type of that object make sense okay let's get rid of one more there other questions yes I missed the last part of that and then if you use default I think you would get that let me just put that code back right so I have R if I remove all these which is unreachable code looks like that works i'm surprised that maybe a bug I may have - I'm going to ask about that one is that if it's null yeah it would pass it would work with null I'm going to ask Neil because I'm not sure how that's the spec deals with that one that is a really interesting case it should do one of the two you're right but I think it would have to ask I saw another hand go up that's a good fun good question yes okay at the moment I haven't gotten working with tuples and I don't know if that's just not in this build or not specified yet okay what about generics let's let's think of what you mean here so let's let me remove the VAR case up here okay and let's say I were to write a yeah so we want a pattern of teeth we're going to take in a T thing right and now we want to switch on thing right mm-hmm so you would want to do something like is if thing what's that I mean that would always pass right because it's generic and if I did a case of T if we did it with so I'm going to switch on thing and here if I did a case T okay that would once again always pass right okay let's just let's just have some fun let's just make this crazy the see if this will compile okay right hmm why dad derives from tea that could be I'm not sure that that's correct either because not all teas are tea derived what's that yeah now it's telling me that this case is handled by a previous case which I don't think is correct this is you guys are smart this is good that's an actual compiler error it's using the same analyzer so yeah if I compile this right now I'm going to get yeah switch case has already been handled by a previous case so we can do things maybe that's another good one I have to ask about that one too so we have two good ones too asks one there and the one about both of our case in the default case cool what happens if I change the argue oh so if I were to change this to object whoo I want to go back I want see what happens if we reverse them okay that makes sense adding that note so I remember that later awesome any other questions okay we're getting close on time and I will be happy to answer any questions up here toward the end and again I will post the slides and the demos sometime later today also make those two pull requests with the code we change later today and [Music] watch on the github repo for comments and changes on any of the new pattern syntax that could be coming soon to a c-sharp near you thank you very much [Applause]
Info
Channel: NDC Conferences
Views: 8,669
Rating: 4.6952381 out of 5
Keywords: ndc, ndc london, bill wagner, patters, c#, .net
Id: 2qf05XALZXo
Channel Id: undefined
Length: 55min 3sec (3303 seconds)
Published: Tue Apr 18 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.