Friday, March 8, 2024

Speaking at DotnetConf 2023 on 28th February 2024, organized by MUGI Bogor

 Hi my readers! This time I spoke at one of .NET themed event, the DotnetConf 2023, on 28th February 2024. I was asked by Fadhil, he is ex-MVP and also a community leader of MUGI Bogor and Buitenzorg (IOT maker community).

Thanks to Microsoft Indonesia for let us borrow some space for the meetup!


This .NET Conf (or DotnetConf) is in sync with annual .NET Conf from Microsoft, and we are always celebrating annual new major release of .NET since .NET 5.0!

Yes, .NET since .NET 5.0 will always have major release annually, with emphasize on both LTS on even version number and STS on odd version number, see also this official blogpost from Microsoft:
https://devblogs.microsoft.com/dotnet/introducing-net-5/ 

Pictures of the event and my presentation

I deliver presentation on What's new in C# 12 in .NET 8.0, using Visual Studio 2022 with .NET 8.0 202 SDK, and I enjoyed the audience attention so much because of the questions that keep coming at me :)

My session is not the first one, so I have got the chance to have pictures with Fadhil and Andik Susilo (the one that presents Monitoring Your Application with AppInsight)



Here's the pic: 




In .NET and C#, language advances are not so aggressive, because language advances must align with .NET CLR, and this is a good practice as .NET has known for its support for multiple programming languages, not just C#.

Yes, I have a demo with solution and projects, here it is the repo:
https://github.com/eriawan/eriawan-speaking-show-materials 

The notable feature is the C# primary constructors. which is taken inspiration from F#.


Here it is the F# counterpart:

Further code sample are also available on my repo.

Happy coding! 





Sunday, December 24, 2023

[ADVENT 2023] Evolving improvement on existing features in the latest F# release in 2023: F# 8.0 (and a surprise)

 Hi my blog readers! Again in this F# Advent 2023, Merry Christmas and happy new year 2023! 😊

Before I discuss the evolving improvements of F#, let's celebrate the release of .NET 8.0 in November this year at dotnetConf 2023! This release has even major version number, means that this is LTS release that is supported for three years since the release.

NOTE about the use of .NET instead of .NET Core: 

If some of you may wonder that I'm not saying ,NET Core 8.0, because Microsoft has removed the "Core" since ,NET 5.0. See also my previous blog at https://fsharpmonologue.blogspot.com/2022/12/advent-2022-whats-new-in-f-7-and-some.html and Microsoft's explanation on why this is just .NET.  This is the last blog that explains why going forward if I discuss .NET 5.0 and later I just use .NET instead of .NET Core.

NOTE about F# 8: F# 8 comes with .NET 8.0 release. Therefore these improvement in F# 8 requires .NET 8.0.

Now, let's discuss what are those improved features in F# 8!

Evolving improvement on F# 8

In this F# Advent, I won't discuss all of new features in F# 8, as all of them has already discussed in this Microsoft blog: https://devblogs.microsoft.com/dotnet/announcing-dotnet-8/ 

What really caught my attention is the way the existing feature evolved: the previous features laid the foundations of the improvement. It is a good thing, as sometimes new features or even improvements may break or even if it may not break sometimes it will create a controversy, as we have seen in C# param bangbang annotation, the "param!!" that now ends up rejected, and the notes of C# LDM meeting about this issue that concludes that this feature is rejected

Let's discuss my own choice of these three feature evolution: F# string interpolation, string literals, numeric literals, and TailCall diagnostic attribute.

F# string interpolation

The F# string interpolation is very useful, and it was introduced in F# 5: Docs on what's new in F# 5

Let's start from a simple usage of this feature, by comparing before and after having string interpolation.

In this example, I print an expression of how many years old Indonesian independence using F# printfn with F# format patterns, before and after F# 5:

sample 1 of string interpolation

As we see, this is very useful and also helpful, in a sense that it simplifies many things, including less use of format patterns. Of course, you can still use the format patterns, like this example from F# docs:





And since F# is strongly typed, this string interpolation combined with format patterns is also strongly typed at compile time. For example, if you mix the format with wrong types you'll get compile error, like this example::



In F# 8, you can use an improvement of having to include the curly braces as part of the string without escaping them, by having extended double dollar signs "$$" instead of just single dollar "$".

This is very convenient when we have a sample to have HTML templating from the blog of F# 8 announcement:






In that sample code, notice that the CSS syntax of curly brace feels more natural in F# 8. This is why I called this evolving feature improvement, instead of introducing new feature that has no or little relation with existing feature!

Here is another sample of F# format patterns combined with string interpolation, this improvement is also amazingly powerful, because the format and the string flow nicely. For example:


String literal improvement

Not just that, the string literal handling is evolved: string literal defined anywhere else can be combined with other string and also format patterns, and also can be used further with printf, sprintf, and other prontf operation. This is also still valid when combined with string interpolation.

For example:

Numeric literals improvement

Numeric literals have been available quite long time, and usually we can only assign a constant value. While we of course can combine these numeric literals with others, in F# 8 the assignment can also contain arithmetic operations. This would be very useful as we could assign calculations of any numeric values as long as the types are correct.

For example (taken from F# code in the announcement of F# 8):







We can now see the evolving of this numeric literals may be minor, but this is very useful indeed. I personally think that this is an added value, and it is similar added value like pervious string literal discussion.

Tailcall diagnostics attribute

We know that we can have recursive functions to be optimized as tailcall functions, especially if the functions meet the conditions to be compiled as recursive functions that has tailcall.

One of the condition is that the function must have accumulator, like the example in F# Tutorial:


Now we could "test" the tailcall. I create the same function with different name and with added TailCall attribute, and then compile it to see if it has warning or not:



There's no warning, and it's a tailcall recursive function. Now if I just use the sample of sum with no tailcall accumulator and mark it, then it give a warning:



Unfortunately, that warning isn't shown in the Visual Studio's Error List pane output:


To see both panes, here it is:


To be honest, this is quite surprising! 

Don't worry, I already submit a bug report: https://github.com/dotnet/fsharp/issues/16467

Monday, December 26, 2022

ADVENT 2022: What's new in F# 7 and some tips (with BenchmarkDotnet)

 Hi my blog audiences! First of all, Merry Christmas for you in this December 2022!

As always, this blog entry in December is part of F# Advent 2022 edition. Thanks Sergey Tihon for tirelessly organizing this F# Advent annually!

As most of you know I often discussing what's new in F# and with the related annual release of .NET. 

NOTES: Now .NET Core is just called .NET, so when I mention .NET this means not .NET Framework, it is .NET Core without Core. See the original Microsoft's official announcement on why.

Before I continue to discuss this F# 7, all of the code/features mentioned here requires Visual Studio 2022 17.4.0 as it is the starting version that provide support for .NET 7.0 SDK. But I strongly recommend to use 17.4.3 and later, because 17.4.3 has many bug fixes and important security fix of CVE-2022-41089 Remote Code Execution .

So what's new in F# 7? Again, there is an official explanation available on Microsoft blog: https://devblogs.microsoft.com/dotnet/announcing-fsharp-7/ and I suggest you to visit that page first.

These are the noticeable new features in F# 7:

  1. Simplified F# SRTP syntax
  2. Static virtual members 
  3. Ability to consume (interop) C# required and init members

And many more as mentioned in the official announcement above.

For this blog entry, I describe static virtual members and consuming C# required and init members.

Static virtual members in F#

The ideas behind this is simple: having static virtual members in interface. As in the blog mentioned, this is needed to consume and also to define static virtual members as those in C#.

    public interface IGetNext where T : IGetNext
    {
        static abstract T operator ++(T other);
    }

For more context, please visit the official doc of this C# 11 feature: https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members

That sample is using complex sample of implementing C# ++ operator, and this is not easily consumed in F#, as F# doesn't have increment operators. 


NOTE: there's a question about this C#'s increment implementation in F# as mentioned in Stackoverflow, but this is not semantically the same as in C#.

Therefore, let's try a simpler one: 

    public interface IFinancialMath
    {
        static abstract double CalculateSavingInterest(double saving, double annualInterestRate, double totalYears);
    }

and then we can consume that in F#:


What about we create in F#? To start quickly, we can use the sample in the blog.

But if we are looking at the F# example in the blog, compiling it will yield warning of FS3535:


But we can suppress this warning in the F# project file (the fsproj) instead of using #nowarn, because it is easier to reason about that this fsproj has disabled warning that we are consciously aware.

NOTE: having nowarn as embedded in the code using #nowarn is still useful if you need granular control on the code instead of project scope in fsproj.

For example:

  
  <PropertyGroup>
	<NoWarn>FS3535</NoWarn>
  </PropertyGroup>

And then it is also simpler, and MSBUILD will recognize that NoWarn to be passed to compiler successfully.

Consuming C# required and init members in F#

Consuming C# required and init members in F# is quite easy, as F# can recognize C# classes/records that has required and init.

NOTE: F# doesn't have the same idiomatic feature of C# required semantics, therefore it is not built in F#. It can only consume C# required semantics, not having its own required semantics.

Large part of this feature has been explained by the blog, but I will add my own additional detail here.

The number of C# members that have required mark will always be checked by F# compiler, but this check will always check each one of the required members. The good thing about this F# support for this feature is the fact that the F# compiler will tell you what are the missing required members to be included as part of the constructor call.

For example: if you have a Person class with 3 required property members, then the compiler will check the required members at the time of calling the constructor. Any missing member will be mentioned by the compiler's error message:

The C# init sample is already described nicely in the blog. This may look like a new feature, as it now recognizes init. But it is not just that: it is actually a matter of change of behavior: previous init was handled as mutating the value of the property and now F# 7 compiler will yield an error if there's an init on the getter and others when the setter and getter is used outside the scope of the init.

This will therefore ensure consistent behavior when these C# codes are used in F#, with the correct behavior as intended.

Comparing LINQ improvements in .NET 7 with equal F# using BenchmarkDotnet

Now that we have .NET 7.0 and F# 7, we also have performance improvements in .NET 7.0. One of the nice improvements is some of LINQ methods are now significantly faster!

In this case, we are going to use very useful and also popular benchmark tool in .NET, using BenchmarkDotnet

Contrary to some of some saying that BenchmarkDotnet is only available (supported) by C#, it isn't that case at all. BenchmarkDotnet can be used by F# (and other managed language such as VB.NET) successfully. It is also open source, therefore it is easy to learn and to contribute back to BenchmarkDotnet.

A good case of this in F# is we are checking the perf improvements in .NET 7.0 against equal function in F#, such as LINQ's Max and F# Array.max.

According to the official announcement of Performance improvements in .NET 7.0, one of the improved LINQ method is Max method. 

Now let's compare this with F# Array.max and see the winner!

Before that, I have to prepare a sample benchmark code, then add BenchmarkDotnet nuget package toat leverage the BenchmarkDotnet. This the simplified code:

And this is the result:


So clearly the winner is LINQ's Max()!

This is interesting, because LINQ Max and Min in .NET 7 is now optimized further to use vectorized API of AVX extension, which is very efficient and also faster.

This means there's still some improvement, particularly on F# implementation of Array.max as we have seen in that benchmark result.

NOTE to myself: I think I will propose this to F# official repo as improvement, therefore it should be on a par with ,NET  LINQ's Max and Min implementations. 😊

That's all folks! Merry Christmas and happy New Year of 2023!



Saturday, December 18, 2021

[ADVENT 2021] Initial quick dive into F# 6 task computation expression

 Hi my blog readers!

First, personal announcement related to this blog:

If you follow my blog, you'll see that this blog is not quite often updated. Because many things have changed: Open Live Writer doesn't support Blogger.com anymore, and Blogger.com itself won't accept OAuth/OpenIDConnect anymore. Therefore I'm still searching for new place to blog, hopefully a new at least affordable, less than 80$ annually.

Ok, back to current blog! 

I'm humbly joining the tradition of F# Advent! This 2021, I focus on what's new on F# 6.0, and some additional notes on some of its new features.

New features of F# 6.0

I'm so excited to try F# 6.0! It is also at the same time of the release of Visual Studio 2022 and .NET 6.0!

Here are the interesting and important new features:

  1. The task { .. } computation expression to support general .NET Task (that usually done in C#/VB.NET)
  2. Simpler collection indexing syntax using expr[idx] instead of expr.[idx]
  3. Struct representations for partial active patterns (new attribute to have marking of partial active pattern as struct)

And others new features

  1. Overloaded custom operations in computation expressions
  2. "as" patterns
  3. Increased consistency of indentation and undentation in code
  4. Additional implicit conversions
  5. New number format for binary 

and many more! For more complete list, visit Microsoft F# Docs: 

https://docs.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-6

Now let's visit F# task computation expression.

F# task computation expression

NOTE: I might be biased, but this new task computation expression is the most important, because it brings closer compatibility with async-task based programming in C# and VB.

We all know that F# already has async computation expression. This existing async computation expression also has convenient functions to interop with Task, such as F#'s Async.StartAsTask:

https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpasync.html#StartAsTask

The task computation exprerssion is better than the existing F# async computation when interop with Task not just the faster performance and easier debugging, but the interop is easier.

The term easier is actually translated as closer compatibility with Task. Why? Let's see the sample code in the Docs:


let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }

We now can call those async API like File.ReadAllBytesAsync(path1) with implicit await by having let! on the returning result.

To see what really happened, the task computation expression comes as TaskBuilder. This builder will generate the necessary IL within the task expression.

Let's see the generated C# decompiler: (I use free JetBrains DotPeek 2021.3)












We could see the similar pattern of C# async in that readFilesTask method  by observing the similar pattern of async state machine.

Then the IL goes further to return task, as in this generated IL method of readFilesTask that returns the Task:

  .method public static class [System.Runtime]System.Threading.Tasks.Task`1
    readFilesTask(
      string path1,
      string path2
    ) cil managed
  {
    .maxstack 4
    .locals init (
      [0] valuetype FSharp6NewFeatures.Say/readFilesTask@11 readFilesTask11,
      [1] valuetype FSharp6NewFeatures.Say/readFilesTask@11& local
    )

    // [23 7 - 23 82]
    IL_0000: ldloca.s     readFilesTask11
    IL_0002: initobj      FSharp6NewFeatures.Say/readFilesTask@11

    // [24 7 - 24 64]
    IL_0008: ldloca.s     readFilesTask11
    IL_000a: stloc.1      // local

    // [26 7 - 26 26]
    IL_000b: ldloc.1      // local
    IL_000c: ldarg.1      // path2
    IL_000d: stfld        string FSharp6NewFeatures.Say/readFilesTask@11::path2

    // [28 7 - 28 26]
    IL_0012: ldloc.1      // local
    IL_0013: ldarg.0      // path1
    IL_0014: stfld        string FSharp6NewFeatures.Say/readFilesTask@11::path1

    // [30 7 - 30 73]
    IL_0019: ldloc.1      // local
    IL_001a: ldflda       valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 FSharp6NewFeatures.Say/readFilesTask@11::Data
    IL_001f: call         valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create()
    IL_0024: stfld        valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::MethodBuilder

    // [32 7 - 32 75]
    IL_0029: ldloc.1      // local
    IL_002a: ldflda       valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 FSharp6NewFeatures.Say/readFilesTask@11::Data
    IL_002f: ldflda       valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::MethodBuilder
    IL_0034: ldloc.1      // local
    IL_0035: call         instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Start(!!0/*valuetype FSharp6NewFeatures.Say/readFilesTask@11*/&)

    // [34 7 - 34 44]
    IL_003a: ldloc.1      // local
    IL_003b: ldflda       valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 FSharp6NewFeatures.Say/readFilesTask@11::Data
    IL_0040: ldflda       valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::MethodBuilder
    IL_0045: call         instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task()
    IL_004a: ret

  } // end of method Say::readFilesTask

Now that we can observe the generated IL after compiling the F# sample, that F# code sample is roughly (semantically) equivalent to this C#:

        public static async Task ReadFilesTask(string path1, string path2)
        {
            var bytes1 = await File.ReadAllBytesAsync(path1);
            var bytes2 = await File.ReadAllBytesAsync(path2);
            var bytes3 = new Byte[bytes1.Length + bytes2.Length];
            // Equivalent logic for F# Array.append
            for (int i = 0; i < bytes1.Length; i++)
                bytes3[i] = bytes1[i];
            for (int i = 0; i < bytes2.Length; i++)
                bytes3[bytes1.Length + i] = bytes2[i];
            return bytes3;
        }

As we see now, it is closer to what C# async has, and this feature also remove barrier to have close compatibility between F# and C# async. 

NOTE: thanks to vibrant F# users, in this first release of F# 6, there is still a bug: any call to Array.map will have undesired exception. The GitHub issue for this bug is available and the merged PR to fix this is also available!

Based on the current progress of that fix, this fix will be available in the upcoming release of .NET 6.0.200 at the same time with VS 2022 17.1.0 release.

But what if you want to use it now? We could just use Don Syme's temporary workaround available on that GitHub issue of that bug.

What are you waiting for? Let's start to code with F# 6.0 now, F# folks! And Merry Christmas and happy holiday! ❤️


Saturday, December 12, 2020

[ADVENT 2020] F# Advent 2020: Revisiting Windows Forms and WPF in .NET 5.0 and hello F# 5.0

 Hi my blog readers!

This year, we have lots of exciting news on .NET Core land: the release of .NET 5.0 and also the release of new language version of F# and C#!

Last year, I discuss about how to write F# code with project support for Windows Forms/WPF in .NET Core 3.1. Also I'll showcase simple sample of latest new F# 5.0 features. What about .NET 5.0?

PS: .NET 5.0 is not .NET Framework. It is actually .NET Core version 5.0, and Microsoft describe as no "Core" branding from .NET 5.0 going forward. See also https://docs.microsoft.com/en-us/dotnet/core/dotnet-five#net-50-doesnt-replace-net-framework

In .NET 5.0, the TFM can be explicitly stated to support the underlying OS. Current .NET Core 3.1 has no TFM with OS directly (so does previous version before 3.1.

This means that in .NET 5.0 and later we should not use .NET Core Windows desktop SDK support like we have in .NET Core 3.1 and 3.0. 

As always, since .NET Core 3.0 and until 5.0, there's no default project template support to create Windows Forms/WPF project using F#. However, we can still code Windows Forms/WPF using the same way we use in my previous F# Advent 2019 blog. Again, with a twist of .NET 5.0 feature 😊

Now let's look at how the current .NET 5.0 create WinForms/WPF project from dotnet CLI.

To create a new winforms app project in .NET 5.0, we can use the same template in 3.1 like this example:

dotnet new winforms -n CSNet50Winforms

Let's look at the generated C# project: 

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

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>

We can see we have two noticeable features:

  1. We can just use default "Microsoft.NET.Sdk" SDK
  2. TFM is set to .NET 5.0 with Windows

Now we apply this to F# project, with the same model of SDK and the TFM as above. The fact is that this SDK project model alongside with TFM is actually not enforcing programming language support, because although Winforms and WPF provides C# and VB support, we can also use the same SDK for F# as well.

Let's start create new console project using F#, and name it FSWindowsDesktop using dotnet CLI:

dotnet new console -n FSWindowsDesktop -lang F#

We'll have this fsproj generated:


Change TFM from net5.0 to net5.0-windows, and also change OutputType to WinExe.

The output type change is important, because we have to explicitly tell the project that we have to mark the resulting Exe as Windows executable to run the app for Windows. See also the technical reason about this change since .NET 5.0: https://docs.microsoft.com/en-us/dotnet/core/compatibility/windows-forms/5.0/automatically-infer-winexe-output-type

Open the Program.fs, then copy the content of Program.fs from my previous .NET Core 3.1, then build it using dotnet build: (I name the project to same FSWindowsDesktop)

dotnet build FSWindowsDesktop\FSWindowsDesktop.fsproj

Then run it! Or, you can also compile and run the project using "dotnet run".

Now we're going to update the code to use one of the cool F# 5.0 features, the string interpolation!

Update the program.fs to be like this:


Note line 21, we use string interpolation to represent the name of the field/variable. It is useful, because now we could avoid compile error especially when the field/variable name changed!

I added the size of the mainForm, because we need to see the title of the form changed to include the string interpolation, combined with other new feature of "nameof". Run the code and we will see this WinForms window:


There you have Windows Forms project in F#! 
This time, with demo of string interpolation, and with also a sample of new "nameof" feature to include the name of the variable/module/symbol as string expression.

As always, the full code is available on my GitHub repo: https://github.com/eriawan/netcore-fsharp-sample

Enjoy and celebrate F# Advent 2020 and happy holiday and Merry Christmas, everyone! 





Wednesday, May 20, 2020

Speaking at DevOps Indonesia joint collaboration with MUGI Jakarta: DevOps Indonesia May 2020 online meetup!

Hi my blog audiences!

If you have followed me last year, I have had collaboration with DevOps Indonesia meetup, now this month I have had another good collaboration with DevOps Indonesia.

On 19th May 2020, we collaborate on having joint collaboration, on behalf of DevOps Indonesia! We were having a wonderful teamwork!
This is the meetup link: https://www.meetup.com/DevOps-Indonesia/events/270642522/



I was participating as speaker to represent MUGI Jakarta, and also helping to give feedback on how the meetup should be, because this is MUGI Jakarta and DevOps Indonesia's first online meetup!

Here, we were using Zoom to host about 50 participants:


This is intriguing! Yes, I have had online remote speaking at Louisville meetup, but now I decided to record the video first and ask Mia and Wisnu from DevOps Indonesia to play the video during my session. This is necessary, because I was having unstable internet the day before the meetup.

There were 2 speakers, one is Gopesh Maindola, a Consulting Engineer from Palo Alto Networks, and then me. I'm proudly represent MUGI Jakarta! 😍

On this meetup, I presented "Adopting shift left and sift right in DevOps". I also gave more sample use cases using the proven best practices from Microsoft, the Azure Cloud design patterns and how Microsoft implements DevOps in its own organization.

First, we have opening from DevOps Indonesia. The team, Wisnu and Mia presenting DevOps Indoonesia meetup timeline:



This is me presenting in recorded video::



I gave use cases from Azure Docs page:



And another nice thing is the online meetup was live on Youtube!
You can watch the full session at: https://www.youtube.com/watch?v=OwqdIHlD0eo

Enjoy the recording video and welcome to "new normal"! 😊



Sunday, March 1, 2020

Representing as Microsoft MVP at DevCon Digital Economy Summit

Hi guys!
I'm humbly proud that I have participated at the DevCon Digital Economy Summit 2020 that hosted by Microsoft Indonesia!
This event is held at Ritz Carlton Hotel, Pacific Place, Jakarta.

I was part of MVP Indonesia team that helps Microsoft Indonesia to promote, spread the event information, and also taking care of special MVP Indonesia's booth.



We are sooo honored to have President of Indonesia, HM Joko Widodo and MS CEO, Satya Nadella!



Here's me and fellow MVPs (Andri Yadi), taking care of MS MVP Booth, answering any questions about software development, .NET, and Visual Studio:




And we (Andri Yadi, Agus Suparno, Adiityo) still had some fun taking pictures with MS Asia's Annie Matthew and MS Indonesia's Irving Hutagalung:



Yup, I'm glad that I could provide help for software developer communities thru this conference! 😉