2009-11-02

Dynamics i C#4.0

Introduktion

Dette er nummer 2 post i en række af artikler om C#4.0. I den første post, har jeg beskrevet, hvordan jeg har konfigureret visual studio 2010 beta 2 express, til at kunne lave kode eksempler. I denne post, vil jeg kigge nærmere på dynamics, som er en af de nye ting i C#4.0

Overblik

Den nye ved typen dynamic er, at instanser af denne type ikke bliver kontrolleret ved kompilering, men først i runtime. Se fx følgende anvendelse af en dynamic variabel

[TestFixture]
public class UseDynamicTest
{
    [Test]
    public void Dynamic_Property_Test()
    {
        UseDynamic sut = new UseDynamic();
        sut.DynamicObject = "Some String";
        sut.DynamicObject *= 4;
        Assert.AreEqual("Some string"*4,sut.DynamicObject);
    }
}

public class UseDynamic
{
    public dynamic DynamicObject { get; set; }
}

Her tildeler jeg først en streng til en dynamic variabel og bagefter forsøger jeg at gange strengen med 4. Dette får jeg ikke nogen kompile fejl ved. Hvis jeg derimod forsøger at udføre det samme direkte på strengen, som jeg har vist i assert'en, så får jeg som forventet med det samme en kompile fejl.

Hvis jeg ændrer min test så den kan kompilere

[Test]
public void Dynamic_Property_Test()
{
    UseDynamic sut = new UseDynamic();
    sut.DynamicObject = "Some String";
    sut.DynamicObject *= 4;
    Assert.AreEqual("Some string" + "Some string" + 
                    "Some string" + "Some string",
                    sut.DynamicObject); 
}

Får jeg følgende fejl, når jeg forsøger at afvikle testen:

1) Test Error : DynamicTest.UseDynamicTest.Dynamic_Property_Test
   Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Operator '*=' cannot be applied to operands of type 'string' and 'int'
   at CallSite.Target(Closure , CallSite , Object , Int32 )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
   at DynamicTest.UseDynamicTest.Dynamic_Property_Test()

Jeg kan derimod sagtens skrive og afvikle følgende
[Test]
public void Dynamic_Property_Test()
{
    UseDynamic sut = new UseDynamic();
    sut.DynamicObject = "Some String";
    Assert.IsTrue(sut.DynamicObject is string);
    sut.DynamicObject = 4;
    Assert.IsTrue(sut.DynamicObject is int);
    Assert.AreEqual(4, sut.DynamicObject);
}

Dvs at den dynamiske variabel først er en streng type og derefter en int type uden at jeg skal udføre nogen typecast.

Anvendelses områder

En af de steder, hvor man fremhæver brugen af dynamics, er i forhold til kald af metoder i dynamiske sprog som fx IronPython og IronRuby. Det lyder umiddelbart ikke noget, man kan bruge i .net verdenen, men da jeg først så, hvad det drejer som om - kunne jeg pludselig se en masse muligheder i det.

Jeg viser her et par eksempler på hvordan man kan kalde metoder i IronPython fra C#. For at kunne dette har jeg hentet IronPython 2.6 CTP for .NET 4.0 Beta 2. Der er i skrivende stund også en 2.6 Release Candidate 2, men den har jeg ikke kunne få til at fungere.

Her er først den python fil, som indeholder det script jeg gerne vil kalde:

# IronPythonFunctions.py
import sys;

# Class
class IronPythonFunctions:
 def __init__(self):
  pass;

 def Add(self,arg1,arg2):
  res=arg1+arg2
  print "Add {0} and {1} - Result is {2}".format(arg1,arg2,res)
  return res

# Factory method
def GetIronPythonFunctions():
 return IronPythonFunctions()

En kort forklaring til denne fil, er at den indeholder en klasse "IronPythonFunctions" med en metode "Add" der tager 2 parametre arg1 og arg2. Desuden indeholder filen en metode der returnerer en instans af klassen "IronPythonFunctions".

Jeg så lavet nogle forskellige test af denne klasse fra C#. For at kunne kalde IronPython fra C# har jeg tilføjet en reference til følgende filer fra IronPython 2.6 CTP for .NET 4.0 Beta 2:
IronPython.dll
IronPython.Modules.dll
Microsoft.Dynamic.dll
Microsoft.Scripting.dll
Microsoft.Scripting.Debugging.dll

Først har jeg testet om jeg kan få returneret en instans af IronPythonFunctions
[Test]
public void Can_Get_IronPythonFunctions_Instance()
{
    var py = Python.CreateRuntime();
    dynamic ironPython = py.UseFile("IronPythonFunctions.py");
    dynamic ironPythonFunctions = ironPython.GetIronPythonFunctions();
    Assert.IsNotNull(ironPythonFunctions);
}

Her efter har jeg testet en række forskellige kald af metoden Add på ironPythonFunctions instancen.
[Test]
public void Can_Add_Numbers()
{
    var py = Python.CreateRuntime();
    dynamic ironPython = py.UseFile("IronPythonFunctions.py");
    dynamic ironPythonFunctions = ironPython.GetIronPythonFunctions();
    Assert.AreEqual(5, ironPythonFunctions.Add(2, 3));
}

[Test]
public void Can_Add_Strings()
{
    var py = Python.CreateRuntime();
    dynamic ironPython = py.UseFile("IronPythonFunctions.py");
    dynamic ironPythonFunctions = ironPython.GetIronPythonFunctions();
    Assert.AreEqual("hello world", ironPythonFunctions.Add("hello ", "world"));
}

[Test]
public void Cannot_Add_Strings_And_Numbers()
{
    var py = Python.CreateRuntime();
    dynamic ironPython = py.UseFile("IronPythonFunctions.py");
    dynamic ironPythonFunctions = ironPython.GetIronPythonFunctions();
    Assert.Throws(typeof(ArgumentTypeException),(()=> ironPythonFunctions.Add("hi ", 5)));
}

Som man kan se, er det rimeligt simpelt at kalde metoder fra IronPython - man skal selvfølgelig passe på og lave grundige tests af sin kode, da man jo først ser om det fungerer runtime. Jeg kunne fx have skrevet følgende test
[Test]
public void Can_Multiply_Numbers()
{
    var py = Python.CreateRuntime();
    dynamic ironPython = py.UseFile("IronPythonFunctions.py");
    dynamic ironPythonFunctions = ironPython.GetIronPythonFunctions();
    Assert.AreEqual(25, ironPythonFunctions.Multiply(5, 5));
}
og stadig kunne kompilere uden fejl. Det er først når jeg kører testen eller i værste tilfælde produktionskoden, at jeg får en fejl.

Dette er en meget kort gennemgang af dynamics, men det viser nogenlunde, hvad man kan anvende det til. Man vil helt sikkert snart se mange flere anvendelses muligheder med dynamics end blot kald til IronPython og lignende.

Næste post

I næste post vil jeg kigge på optionelle parametre og hvad de kan bruges til.

2 kommentarer:

  1. Super-spændende indlæg :-)

    Jeg har ikke selv forsøgt at kalde Python fra C#, men jeg kan helt sikkert se fordelen i at bruge dynamics mod andre unmanaged frameworks.

    Men samtidig synes jeg også det er et ret udfordrende paradigme-skift i måden at opfatte C# på. Jeg kan som udvikler godt se ideen med dynamics og endnu vigtigere føler jeg mig i stand til at prioritere hvor det giver mest mening at anvende. Men der er altså unægtelig forskel på den "modeltolkning" en udvikler vælger ved eksempelvis at bruge en generisk liste af MyType og en generisk liste af dynamics, og jeg kan ikke helt slippe tanken om at dynamic på en måde er med til at afhjælpe udfordrende integrationspunkter til legacy- eller ufleksible komponenter. Hvis C# virkelig var vejen frem for Microsoft, burde integrationsbyrden så ikke ligge hos de øvrige sprog (ikke nødvendigvis for Python men eksempelvis med Office integration)? Skal vi til at opgive visionen med generic og typestærke erklæringer og gøre plads til "any type"?

    SvarSlet
  2. Hej Stig,

    Jeg synes også at man skal være forsigtig med at bruge dynamics, i almindelig dagligdags udviklings opgaver, især, hvis løsningen sagtens kan løses i "ren" C#. Med hensyn til fx office integration så er der jo også VSTO, hvor man jo har fjernet meget af det besværlige med at kommunikere med office komponenter. Jeg har også talt med nogen, der mener at dette med dynamics, svarer til "typen" variant i VB6. Det synes jeg dog efter at have kigget nærmere på det ikke er tilfældet - jeg faldt forleden over endnu en ting, som udnytter dynamic typen, nemlig et expandoobject. Jeg har ikke fået kigget så meget på det endnu - men jeg synes at det også er en ,for mig, ny måde at udnytte dynamic. Man kan fx bruge det til at lave en liste af typestærke objekter inline i koden (der dog først bliver genereret runtime) istedet for at fx bruge en dictionary, hvor man jo kun har en key og en value at gøre godt med.

    Men mange tak for din kommentar. Det er rart når ens slid med at forsøge at forstå og beskrive nye ting, også kan bruges af andre.

    SvarSlet