日期:2020-06-06  浏览次数:1258 次

Chapter 20 - Dynamic Programming

Numeric type unification

static dynamic Mean (dynamic x, dynamic y) => (x + y) / 2;

void Main()
{ 
  int x = 3, y = 5;
  Console.WriteLine (Mean (x, y));
}

Numeric type unification - typesafe

static T Mean<T> (T x, T y)
{
  dynamic result = ((dynamic) x + y) / 2;
  return (T) result;
}

void Main()
{ 
  int x = 3, y = 5;
  Console.WriteLine (Mean (x, y));
}

Numeric type unification - with static overloads

static T Mean<T> (T x, T y)
{
  "Dynamic".Dump();
  dynamic result = ((dynamic) x + y) / 2;
  return (T) result;
}

static double Mean (double x, double y)
{
  "Static".Dump();
  return (x + y) / 2;
}

void Main()
{ 
  Mean (3, 4).Dump();
  Mean (3.0, 4.0).Dump();
}

Visitor pattern

class ToXElementPersonVisitor
{
  public XElement DynamicVisit (Person p)  
  {
    return Visit ((dynamic)p);
  }
  
  XElement Visit (Person p)
  {
    return new XElement ("Person",
      new XAttribute ("Type", p.GetType().Name),
      new XElement ("FirstName", p.FirstName),
      new XElement ("LastName", p.LastName),
      p.Friends.Select (f => DynamicVisit (f))
    );
  }
  
  XElement Visit (Customer c)   // Specialized logic for customers
  {
    XElement xe = Visit ((Person)c);   // Call "base" method
    xe.Add (new XElement ("CreditLimit", c.CreditLimit));
    return xe;
  }
  
  XElement Visit (Employee e)   // Specialized logic for employees
  {
    XElement xe = Visit ((Person)e);   // Call "base" method
    xe.Add (new XElement ("Salary", e.Salary));
    return xe;
  }
}

void Main()
{
  var cust = new Customer { FirstName = "Joe", LastName = "Bloggs", CreditLimit = 123 };
  cust.Friends.Add (new Employee { FirstName = "Sue", LastName = "Brown", Salary = 50000 });  
  new ToXElementPersonVisitor().DynamicVisit (cust).Dump();  
}

class Person
{
  public string FirstName { get; set; }
  public string LastName  { get; set; }

  // The Friends collection may contain Customers & Employees:
  public readonly IList<Person> Friends = new Collection<Person> ();
}

class Customer : Person { public decimal CreditLimit { get; set; } }
class Employee : Person { public decimal Salary      { get; set; } }

Visitor pattern - with abstract base class

abstract class PersonVisitor<T>
{
  public T DynamicVisit (Person p) { return Visit ((dynamic)p); }
  
  protected abstract T Visit (Person p);
  protected virtual T Visit (Customer c) { return Visit ((Person) c); }
  protected virtual T Visit (Employee e) { return Visit ((Person) e); }
}

class ToXElementPersonVisitor : PersonVisitor<XElement>
{
  protected override XElement Visit (Person p)
  {
    return new XElement ("Person",
    new XAttribute ("Type", p.GetType().Name),
    new XElement ("FirstName", p.FirstName),
    new XElement ("LastName", p.LastName),
    p.Friends.Select (f => DynamicVisit (f))
    );
  }
  
  protected override XElement Visit (Customer c)
  {
    XElement xe = base.Visit (c);
    xe.Add (new XElement ("CreditLimit", c.CreditLimit));
    return xe;
  }
  
  protected override XElement Visit (Employee e)
  {
    XElement xe = base.Visit (e);
    xe.Add (new XElement ("Salary", e.Salary));
    return xe;
  }
}

void Main()
{
  var cust = new Customer { FirstName = "Joe", LastName = "Bloggs", CreditLimit = 123 };
  cust.Friends.Add (new Employee { FirstName = "Sue", LastName = "Brown", Salary = 50000 });  
  new ToXElementPersonVisitor().DynamicVisit (cust).Dump();  
}

class Person
{
  public string FirstName { get; set; }
  public string LastName  { get; set; }

  // The Friends collection may contain Customers & Employees:
  public readonly IList<Person> Friends = new Collection<Person> ();
}

class Customer : Person { public decimal CreditLimit { get; set; } }
class Employee : Person { public decimal Salary      { get; set; } }

Anonymously calling members of generic type

public class Foo<T> { public T Value; }

void Write (dynamic obj)
{
  try 
  {
    Console.WriteLine (obj.Value);
  }
  catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
  {
    "Member doesn't exist".Dump();
  }
}

void Main()
{
  Write (new Foo<int> { Value = 123 });
  Write (new Foo<string> { Value = "foo" });
}

Anonymously calling members of generic type with multiple dispatch

public class Foo<T> { public T Value; }

void Write (dynamic obj)
{
  object result = GetFooValue (obj);
  if (result != null) Console.WriteLine (result);
}

static T GetFooValue<T> (Foo<T> foo) { return foo.Value; }
static object GetFooValue (object foo) { return null; }

void Main()
{
  Write (new Foo<int> { Value = 123 });
  Write (new Foo<string> { Value = "foo" });
}

Anonymously calling members of generic type - IGrouping

string GetGroupKey<TKey,TElement> (IGrouping<TKey,TElement> group)
{
  return "Group with key=" + group.Key + ": ";
}

string GetGroupKey (object source) { return null; }

string ToStringEx (object value)
{
  if (value == null) return "<null>";
  if (value is string) return (string) value;
  if (value.GetType().IsPrimitive) return value.ToString();
  
  StringBuilder sb = new StringBuilder();
  
  string groupKey = GetGroupKey ((dynamic)value);   // Dynamic dispatch
  if (groupKey != null) sb.Append (groupKey);
  
  if (value is IEnumerable)
    foreach (object element in ((IEnumerable)value))
      sb.Append (ToStringEx (element) + " ");
  
  if (sb.Length == 0) sb.Append (value.ToString());
  
  return "\r\n" + sb.ToString();
}

void Main()
{
  ToStringEx ("xyyzzz".GroupBy (c => c)).Dump();
}

DynamicObject - dynamic XAttributes

static class XExtensions
{
  public static dynamic DynamicAttributes (this XElement e)
  {
    return new XWrapper (e);
  }
  
  class XWrapper : DynamicObject
  {
    XElement _element;
    public XWrapper (XElement e) { _element = e; }
  
    public override bool TryGetMember (GetMemberBinder binder,
                    out object result)
    {
      result = _element.Attribute (binder.Name).Value;
      return true;
    }
  
    public override bool TrySetMember (SetMemberBinder binder,
                    object value)
    {
      _element.SetAttributeValue (binder.Name, value);
      return true;
    }
  }
}

void Main()
{
  XElement x = XElement.Parse (@"<Label Text=""Hello"" Id=""5""/>");
  dynamic da = x.DynamicAttributes();
  Console.WriteLine (da.Id);           // 5
  da.Text = "Foo";
  Console.WriteLine (x.ToString());    // <Label Text="Foo" Id="5" />
}

DynamicObject - dynamic DataReader

void Main()
{  
  var command = new SqlCommand ("select top 3 * from Purchase", (SqlConnection) Connection);
  Connection.Open();
  using (IDataReader reader = command.ExecuteReader (CommandBehavior.CloseConnection))
  {
    dynamic dr = new DynamicReader (reader);
    while (reader.Read())
    {
      int id = dr.ID;
      string description = dr.Description;
      DateTime date = dr.Date;
      decimal price = dr.Price;
      new { description, date, price }.Dump();
    }
  }
}

public class DynamicReader : DynamicObject
{
  readonly IDataRecord _dataRecord;
  public DynamicReader (IDataRecord dr) { _dataRecord = dr; }
  
  public override bool TryGetMember (GetMemberBinder binder,
                    out object result)
  {
    result = _dataRecord [binder.Name];
    return true;
  }
}

DynamicObject - TryBinaryOperation and TryInvoke

static void Main()
{
  dynamic d = new Duck();
  Console.WriteLine (d + d);          // foo
  Console.WriteLine (d (78, 'x'));    // 123
}

public class Duck : DynamicObject
{
  public override bool TryBinaryOperation (BinaryOperationBinder binder,
                      object arg, out object result)
  {
    Console.WriteLine (binder.Operation);   // Add
    result = "foo";
    return true;
  }
  
  public override bool TryInvoke (InvokeBinder binder,
                  object[] args, out object result)
  {
    Console.WriteLine (args[0]);    // 78
    result = 123;
    return true;
  }
}

ExpandoObject

dynamic x = new ExpandoObject();
x.FavoriteColor = ConsoleColor.Green;
x.FavoriteNumber = 7;
Console.WriteLine (x.FavoriteColor);    // Green
Console.WriteLine (x.FavoriteNumber);   // 7

var dict = (IDictionary<string,object>) x;
Console.WriteLine (dict ["FavoriteColor"]);    // Green
Console.WriteLine (dict ["FavoriteNumber"]);   // 7
Console.WriteLine (dict.Count);                // 2

Python calculator

static void Main()
{
  int result = (int) Calculate ("2 * 3");
  Console.WriteLine (result);              // 6
  
  var list = (IEnumerable) Calculate ("[1, 2, 3] + [4, 5]");
  foreach (int n in list) Console.Write (n);  // 12345
}
  
static object Calculate (string expression)
{
  ScriptEngine engine = Python.CreateEngine();
  return engine.Execute (expression);
}

Python interop - passing state

// The following string could come from a file or database:
string auditRule = "taxPaidLastYear / taxPaidThisYear > 2";

ScriptEngine engine = Python.CreateEngine ();    

ScriptScope scope = engine.CreateScope ();        
scope.SetVariable ("taxPaidLastYear", 20000m);
scope.SetVariable ("taxPaidThisYear", 8000m);

ScriptSource source = engine.CreateScriptSourceFromString (auditRule, SourceCodeKind.Expression);

bool auditRequired = (bool) source.Execute (scope);
Console.WriteLine (auditRequired);   // True

Python interop - returning variables

string code = "result = input * 3";

ScriptEngine engine = Python.CreateEngine();

ScriptScope scope = engine.CreateScope();
scope.SetVariable ("input", 2);

ScriptSource source = engine.CreateScriptSourceFromString (code, SourceCodeKind.SingleStatement);
source.Execute (scope);
Console.WriteLine (scope.GetVariable ("result"));   // 6

Python interop - type marshaling

string code = @"sb.Append (""World"")";

ScriptEngine engine = Python.CreateEngine ();

ScriptScope scope = engine.CreateScope ();
var sb = new StringBuilder ("Hello");
scope.SetVariable ("sb", sb);

ScriptSource source = engine.CreateScriptSourceFromString (code, SourceCodeKind.SingleStatement);
source.Execute (scope);
string s = sb.ToString();
s.Dump ("StringBuilder");