Chapter 2
Using Compound Type Initialization

In This Chapter

Image Initializing Objects with Named Types

Image Initializing Anonymous Types

Image Initializing Collections

Image Using Conversion Operators

“Diplomacy is the art of saying ‘Nice doggie’ until you can find a rock.”

—Will Rogers

It is valid to wonder how much work the compiler writer should train the compilers to do for us. The answer might have to be as much as possible that doesn’t immediately add to more confusion. (And, saving work is worth a little confusion.)

Some programmers might bridle at the notion of Microsoft (or any compiler writer) taking some work and control out of the hands of programmers. However, if you consider present-day software development, many layers of code are sitting on top of the hardware. These days, programmers generally do not write much assembly code. The Windows application programming interface (API) assembled a lot of disparate function calls into functions that manage many of the rudimentary tasks of Windows programming, and the .NET Framework organizes all of that code into a highly organized object hierarchy. Further, components, controls, and services sit on top of all of this. Yet, programmers still have more code than ever to write on tighter schedules with increasing complexity. And, arguably, software lags hardware; that is, our computers are capable of much more than our programs are asking of them.

All taken together, it means that compiler writers have to do as much work for us as possible and as is useful. This means if you can skip writing things because the compiler can determine what is needed then, the compiler writer should add this behavior. Compilers doing things for us is especially innocuous if we can choose not to use compiler shortcuts.

This chapter covers compound type initialization. In part, compound initialization eliminates the need to write every flavor of constructor you might need because the .NET 3.5 compiler can infer constructor behavior based on named-type initializing and this code is arguably as clear as verbose, hand-crafted constructors. Initialization is essential for LINQ queries and useful for collections, so those features are discussed, too. In addition, as promised, the little extra rolled in is coverage of conversion operators.

Because Hello, World! applications can get a little dull after a while, this chapter includes the complete implementation of a toy like the Spirograph called Hypergraph. Hypergraph draws hypotrochoidal and epitrochoidal figures—or pretty shapes based on mathematical curves—using compound initialization, LINQ queries, and conversion operators.

Initializing Objects with Named Types

Object-initializer syntax permits creating objects and assigning values to fields in a shorthand notation without requiring that specific constructors be defined by the programmer to get the assignment done. Listing 2.1 shows object construction with a default constructor and property assignment. Listing 2.2 shows construction of an object using a constructor defined for the purpose, and Listing 2.3 shows object initialization using the new capability of .NET 3.5. (All of the samples are a derivative of the Hypergraph solution available for download.)

Listing 2.1 Construction of Two Objects and Property Initialization Using a Verbose Style of Coding

private void Form2_Paint(object sender, PaintEventArgs e)
{
  ColoredPoint p1 = new ColoredPoint();
  p1.MyColor = Color.Red;
  p1.MyPoint = new Point(50,50);

  ColoredPoint p2 = new ColoredPoint();
  p2.MyColor = Color.Red;
  p2.MyPoint = new Point(75, 75);
  e.Graphics.DrawLine(Pens.Red, p1.MyPoint, p2.MyPoint);
}

Listing 2.2 Construction of a Pen Object 5 Pixels Wide Using a Constructor with Defined Parameters

private void Form2_Paint(object sender, PaintEventArgs e)
{
  ColoredPoint p1 = new ColoredPoint();
  p1.MyColor = Color.Red;
  p1.MyPoint = new Point(50,50);

  ColoredPoint p2 = new ColoredPoint();
  p2.MyColor = Color.Red;
  p2.MyPoint = new Point(75, 75);

  Pen pen = new Pen(Color.FromArgb(255, 0, 0), 5);
  e.Graphics.DrawLine(pen, p1.MyPoint, p2.MyPoint);
}

Listing 2.3 The Paint Event Handler Using Object Initialization with Named Types—the Name of the Properties—Instead of a Constructor and Parameters

private void Form2_Paint(object sender, PaintEventArgs e)
{
  ColoredPoint p1 = new ColoredPoint{MyColor=Color.Red, MyPoint=new Point(50,50)};
  ColoredPoint p2 = new ColoredPoint{MyColor=Color.Red, MyPoint=new Point(75, 75)};
  Pen pen = new Pen(Color.FromArgb(255, 0, 0), 5);
  e.Graphics.DrawLine(pen, p1.MyPoint, p2.MyPoint);
}

The code in Listing 2.3 is, effectively, emitted as if it had been written like the code in Listing 2.1. Also, the code in Listing 2.3 looks very similar to how ColoredPoint objects are constructed with constructors with parameters. The difference is that you do not need to write the verbose form in Listing 2.1, nor do you need to write the constructor, but the intent of this code is as clear as if you had written a two-parameter constructor or the long version (like Listing 2.1).

Listing 2.3 eliminates the long form of construction and writing the constructor—with all the benefits of having done so. Because a lot of what we do in .NET is write constructors and initialize objects, object initialization with named types saves us from writing a lot of dull code.

The basic behavior of using initialization with named types is to use ordinary object declaration, or anonymous type declaration and the new keyword and type on the left side of the operator. However, instead of using parentheses and parameter values, you can use property names, the assignment operator, and the value. The general syntax of named type initialization is as follows:

Class variable = new Class{Property1=value1, etc};

See Listing 2.3 for an example. When you use this form of object initialization, you can selectively choose to provide initializers for only those properties you are interested in.

Implementing Classes for Compound Initialization Through Named Types

Using named parameters is not completely new to .NET. Named types have been used to initialize attributes since .NET 1.0. Initializing objects by naming public properties rather than parameters to a constructor looks natural and is intuitive. This feature is nice syntactic sugar for everyday types but is critical for anonymous types returned from LINQ queries.

With queries, named initialization lets us pick and choose the properties in the source and map them to new property names in the target of the query, a projection. (We’ll return to this subject in a minute.) To demonstrate named typed initialization, take a look at Listing 2.4, which shows the ColoredPoint class for the Hypergraph and notice how you can pick and choose which properties to initialize when you construct ColoredPoint objects. (Listing 2.4 contains the complete ColoredPoint class. ColoredPoint is a wrapper class for a Point and a Color.)

Listing 2.4 The ColoredPoint Class for the Hypergraph Is Used to Represent Colored Points

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Hypergraph
{
  public class ColoredPoint
  {
    // Auto-implemented property
    public Color MyColor{ get; set; }

    // can’t use because we need to modify X and Y properties
    private PointF myPoint;
    public PointF MyPoint
    {
      get
      {
        return myPoint;
      }
      set
      {
        myPoint = value;
      }
}

public float X
    {
      get
      {
        return myPoint.X;
      }
      set
      {
        myPoint.X = value;
      }
    }

    public float Y
    {
      get
      {
        return myPoint.Y;
      }
      set
      {
        myPoint.Y = value;
      }
    }

    /// <summary>
    /// Initializes a new instance of the ColoredPoint class.
    /// </summary>
    public ColoredPoint()
    {
    }

    public override string ToString()
    {
      return MyPoint.ToString();
    }

    internal ColoredPoint Clone()
    {
      return new ColoredPoint{MyColor=this.MyColor, MyPoint=this.MyPoint};
    }
  }
}

The ColoredPoint class supports storing a Point and a Color. (The Color exists to support multicolored mathematical shapes but is not used.) More important, note that there is no constructor defined that accepts a Color or Point. They aren’t needed. You can use named-typed initialization and just not write that code.

Named-type initialization is demonstrated in the Clone method. Clone names the properties to initialize and makes a copy of the ColoredPoint object. (In practice, you could use the predefined MemberWiseClone object for a shallow copy instead of writing your own Clone procedure.)

There is another interesting feature in Listing 2.4, the MyColor property. MyColor uses auto-implemented properties, which are covered in the next section.

Understanding Auto-Implemented Properties

MyColor has an empty getter and setter. At first, it looks just like a property definition in an interface. However, when the .NET 3.5 compiler sees this code, it implements the property automatically, hence the nom de guerre. If you don’t need to access a field directly and you are just getting and setting an underlying field property, which is pretty common, you can let the .NET compiler implement a private backing field for you. When you use auto-implemented properties, the private backing field is only accessible through the getter and setter.

Attributes are not allowed on auto-implemented properties, so roll your own if you need an attribute. Also, you can implement read-only auto-implemented properties by prefixing the set; keyword with the private modifier.

public type name{ get; private set;}

Finally, notice that Listing 2.4’s ColoredPoint class does not use the auto-implemented feature for the MyPoint property. This is because the constituent properties of the Point class, X and Y, were surfaced and the field myPoint was referred to. Even if you used an auto-implemented property for MyPoint and referred to the property MyPoint.X to surface the constituent property X, it’s a compiler error. If you do more than set a field, implement the property yourself.

Initializing Anonymous Types

Anonymous types can only be initialized with object initializer syntax. That’s because there is no formally defined type until the code is compiled. Object initialization can also be used to change the property names in the anonymous type.

This ability to query existing types, rename (or project) a new name on the anonymous type’s properties, is referred to as shaping and the resultant anonymous type is referred to as a projection. The code in Listing 2.5 uses a query to project the generic List<Person> and projects it to an anonymous type referred to by names and changes the Name property in the Person class to FirstName.

Listing 2.5 Projecting a New Type from Person and Renaming a Property in the Initializer Clause

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AnonymousTypeInitializer
{
  class Program
  {
    static void Main(string[] args)
    {
      List<Person> people = new List<Person>
      {
        new Person{ID=1, Name=”Paul”, Email=”[email protected]”},
        new Person{ID=2, Name=”Joe”, Email=”[email protected]”}
      };

      var names = from p in people select new {FirstName=p.Name};
      foreach(var name in names)
        Console.WriteLine(name.FirstName);
      Console.ReadLine();
    }
}

public class Person
{
  private int iD;
  public int ID
  {
    get { return iD; }
    Set { iD = value; }
  }

  private string name;
  public string Name
  {
    get { return name; }
    set { name = value; }
  }
  private string email;
  public string Email
  {
    get { return email; }
    set { email = value; }
  }
 }
}

Listing 2.5 defines a Person class. The statement beginning with var names uses a LINQ query to select all of the Name values. (Don’t worry about the query too much right now.) The initializer {FirstName=p.Name} means to initialize the resultset as an anonymous type with a property named FirstName. The foreach statement shows how we now refer to the Name values through the projected FirstName property.

If you leave the name part out of the initialization clause, the projected type uses the same name as the initializing type. In Listing 2.5, this means objects in the anonymous types in names would have a Name property.

Initializing Collections

Initialization syntax is extended to collections, too. In previous versions of .NET, when you wanted to create Lists of objects, you first created the list, then each object, adding the objects one at a time to the collection (List or whatever). Listing 2.5’s initialization of the people List demonstrates how to initialize the List<Person> and all of the Person objects (also using initialization syntax), adding all of the Person objects to the List in one fell swoop.

If you use a tool like Lutz Roeder’s .NET Reflector (see Figure 2.1), you can quickly determine that the compiler has converted the collection initialization code from Listing 2.5 into the long form of List construction, object construction, and calls to the List’s Add method. Over the course of an application collection initialization, you will save many lines of code.

Figure 2.1 Reflector’s disassembler clearly shows that the collection initialization code is converted by the compiler into the verbose form of construction and a call to Add.

Image

You can combine object initialization and LINQ queries and implement the ColoredPointList. ColoredPointList (Listing 2.6) inherits from List<T> and uses a query and a conversion operator to convert the typed list of ColoredPoint objects to an array of PointF objects. This approach was used because it is very easy to store objects in Lists, but GDI+ uses arrays of points to draw arcs, which is what the Hypergraph images (see Figure 2.2) are composed of.

Listing 2.6 The ColoredPointList Uses Queries and Conversion Operators to Make Storing the Points Easy and Converting Them to a Form Necessary for GDI+’s Drawing Methods

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Hypergraph
{
  public class ColoredPointList : List<ColoredPoint>
  {
  /// <summary>
  /// Use conversion operator ToArray
  /// </summary>
  /// <returns></returns>
  public PointF[] GetPoints()
  {
    return (from p in this select p.MyPoint).ToArray<PointF>();
  }

  public ColoredPointList Clone()
  {
    ColoredPointList list = new ColoredPointList();
    list.AddRange((from o in this select o.Clone()));
    return list;
  }
 }
}

Figure 2.2 The Hypergraph toy with one of the rendered images.

Image

The GetPoints method uses a LINQ query that selects all of the Point objects from all of the ColoredPoints. The query actually returns an instance of System.Linq.Enumerable.SelectIterator<T> of ColoredPoints and when you access this iterator, you get the types described in the select clause. ToArray<T> is a conversion operator (via an extension method) that converts the elements of the query to an array. Extension methods are discussed in more detail in Chapter 3, “Defining Extension and Partial Methods.”

Finishing the Hypergraph

To help you explore the Hypergraph, Listing 2.7 includes the code for the Hypergraph itself and Listing 2.8 contains the IHypergraph interface. The interface is used as a means of associating Hypergraph with the slider controls (on a UserControl), shown at the bottom of Figure 2.2.

Listing 2.7 The Complete Listing of the Hypergraph Class

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Hypergraph
{
  public class Hypergraph : IHypergraph
  {
    private static Random random = new Random(DateTime.Now.Millisecond);
    private static float NextRandom(float scalar)
    {
      return (float)random.NextDouble() * scalar;
    }
    private static int NextRandom(int scalar)
    {
      return random.Next(scalar);
    }

    /// <summary>
    /// Didn’t use auto-implemented fields here by choice
    /// </summary>
    private float angle = NextRandom(5f);
    public float Angle
    {
      get {  return angle; }
      set { angle = value;  }
    }
    private float speed = NextRandom(1f);
    public float Speed
    {
      get {  return speed; }
      set { speed = value;  }
    }
    private float centerX = NextRandom(200f);
    public float CenterX
    {
      get {  return centerX; }
      set { centerX = value;  }
    }
    private float centerY = NextRandom(200f);
    public float CenterY
    {
      get {  return centerY; }
      set { centerY = value;  }
    }
    private float innerRingRadius = NextRandom(81f);
    public float InnerRingRadius
    {
      get {  return innerRingRadius; }
      set { innerRingRadius = value;  }
    }
    private float outerRingRadius = NextRandom(100f);
    public float OuterRingRadius
    {
      get {  return outerRingRadius; }
      set { outerRingRadius = value;  }
    }
    private float angleOfPen = NextRandom(3.5f);
    public float AngleOfPen
    {
      get {  return angleOfPen; }
      set { angleOfPen = value;  }
    }
    private float penRadialDistanceFromCenter = NextRandom(70f);
    public float PenRadialDistanceFromCenter
    {
      get {  return penRadialDistanceFromCenter; }
      set { penRadialDistanceFromCenter = value;  }
    }

    /// <summary>
    /// Can’t use auto-implemented type here because we have behaviors
    /// </summary>
    private Color penColor = Color.FromArgb(NextRandom(255), NextRandom(255), NextRandom(255));
    public Color PenColor
    {
      get {  return penColor; }
      set
      {
        penColor = value;
        MyPen = new Pen(value);
      }
    }

    /// <summary>
    /// Auto-implemented field only accessible here
    /// </summary>
    public Pen MyPen{ get; set; }

    /// <summary>
    /// No auto-implemented fields here because construction of object field
    /// is easier without it
    /// </summary>
    private ColoredPointList list = new ColoredPointList();
    public ColoredPointList List
    {
      get {  return list; }
    }

    public void Clear()
    {
      Broadcaster.Broadcast(“Clear”);
      list.Clear();
    }

    public PointF[] Points
    {
      get {  return list.GetPoints(); }
    }
    public ColoredPoint PlotNextPoint()
    {
      angle += speed;
      angleOfPen = angle - angle * outerRingRadius / innerRingRadius;

      // object initializer with named types
      ColoredPoint point = new ColoredPoint{MyColor=penColor,
          X=GetX(), Y=GetY()};

      list.Add(point);
      const string mask = “Step: {0}”;
      Broadcaster.Broadcast(mask, point.ToString());
      return point;
    }

    private float GetCsx()
    {
      return (float)(centerX + Math.Cos(angle)
        * (outerRingRadius - innerRingRadius));
    }

    private float GetCsy()
    {
      return (float)(centerY + Math.Sin(angle)
        * (outerRingRadius - innerRingRadius));
    }

    private float GetX()
    {
      return (float)(GetCsx() + Math.Cos(angleOfPen)
        * penRadialDistanceFromCenter);
    }

    private float GetY()
    {
      return (float)(GetCsy() + Math.Sin(angleOfPen) *
        penRadialDistanceFromCenter);
    }

    public RectangleF OuterRingBoundsRect()
    {
      return new RectangleF(centerX-outerRingRadius,
        centerY - outerRingRadius,
        2 * outerRingRadius, 2 * OuterRingRadius);
    }

    public RectangleF GetAxis()
    {
      return new RectangleF(GetX(), GetY(),
        innerRingRadius, innerRingRadius);
    }

    public RectangleF InnerRingBoundsRect()
    {
      return new RectangleF(GetCsx() - innerRingRadius,
        GetCsy() - innerRingRadius, 2 * innerRingRadius,
        2 * innerRingRadius);
    }

    /// <summary>
    /// Initializes a new instance of the Hypergraph class.
    /// </summary>
    /// <param name=”angle”></param>
    /// <param name=”speed”></param>
    /// <param name=”centerX”></param>
    /// <param name=”centerY”></param>
    /// <param name=”innerRingRadius”></param>
    /// <param name=”outerRingRadius”></param>
    /// <param name=”angleOfPen”></param>
    /// <param name=”penRadialDistanceFromCenter”></param>
    /// <param name=”penColor”></param>
    /// <param name=”list”></param>
    public Hypergraph(float angle, float speed,
      float centerX, float centerY, float innerRingRadius,
      float outerRingRadius, float angleOfPen,
      float penRadialDistanceFromCenter, Color penColor,
      ColoredPointList list)
    {
        this.angle = angle;
        this.speed = speed;
        this.centerX = centerX;
        this.centerY = centerY;
        this.innerRingRadius = innerRingRadius;
        this.outerRingRadius = outerRingRadius;
        this.angleOfPen = angleOfPen;
        this.penRadialDistanceFromCenter = penRadialDistanceFromCenter;
        this.penColor = penColor;
        this.list = list;
    }

    /// <summary>
    /// Initializes a new instance of the Hypergraph class.
    /// </summary>
    public Hypergraph(Hypergraph o)
    {
        this.angle = o.angle;
        this.speed = o.speed;
        this.centerX = o.centerX;
        this.centerY = o.centerY;
        this.innerRingRadius = o.innerRingRadius;
        this.outerRingRadius = o.outerRingRadius;
        this.angleOfPen = o.angleOfPen;
        this.penRadialDistanceFromCenter = o.penRadialDistanceFromCenter;
        this.penColor = o.penColor;
        this.list = o.List.Clone();
    }
/// <summary>
/// Initializes a new instance of the Hypergraph class.
 /// </summary>
public Hypergraph()
{
MyPen = new Pen(Color.FromArgb(NextRandom(255), NextRandom(255),
NextRandom(255)));
}
public Hypergraph Clone()
{
return new Hypergraph(this);
}
private GraphicsPath path = new GraphicsPath();
public GraphicsPath Path
{
get
{
path.Reset();
if(List.Count > 2) path.AddPolygon(Points);
path.CloseAll();
return path;
}
}
public void SaveToyToFile(string filename)
{
Broadcaster.Broadcast(–Saving to {0}—, filename);
Hypergraph copy = this.Clone();
    foreach (var p in copy.List)
    {
      p.MyPoint = new PointF(p.MyPoint.X - copy.CenterX +
        (copy.OuterRingRadius + copy.InnerRingRadius) / 2,
        p.MyPoint.Y - copy.CenterY +
        (copy.OuterRingRadius + copy.InnerRingRadius) / 2);
    }

    GraphicsPath path = copy.Path;
    RectangleF bounds = path.GetBounds();
    Bitmap bitmap = new Bitmap((int)bounds.Width, (int)bounds.Height);
    Graphics graphics = Graphics.FromImage(bitmap);
    graphics.DrawCurve(MyPen, copy.Points);
    bitmap.Save(filename, ImageFormat.Jpeg);
    Broadcaster.Broadcast(“Saved”);
  }

  public void Draw(Graphics g)
  {
    if(Points.Length < 2) return;
    Broadcaster.Broadcast(“Drawing”);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.DrawCurve(MyPen, this.Points);
    if (toyVisible) ShowToy(g);
  }

  private void ShowToy(Graphics g)
  {
    g.DrawEllipse(Pens.Silver, OuterRingBoundsRect());
    g.DrawEllipse(Pens.Silver, InnerRingBoundsRect());
    RectangleF rect = GetAxis();
    g.FillEllipse(new SolidBrush(PenColor), rect.X, rect.Y, 6, 6);
  }

  internal void CenterImage(int width, int height)
  {
    Clear();
    CenterX = width / 2;
    CenterY = height / 2;
    Broadcaster.Broadcast(“Centered image at {0} x {1}”, CenterX, CenterY);
  }

  #region IHypergraph Members

  private bool toyVisible;
  public bool ToyVisible
  {
    get
    {
      return toyVisible;
    }
    set
    {
      toyVisible = value;
    }
  }

  #endregion

  internal void Plot(int p)
  {
    for(var i=0; i<p; i++)
      this.PlotNextPoint();
  }
 }
}

Listing 2.8 The IHypergraph Interface Is Used to Support the Observer Pattern Between the UserControl with Sliders and the Hypergraph Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Text;

namespace Hypergraph
{
  public interface IHypergraph
  {
    float InnerRingRadius { get; set; }
    float OuterRingRadius { get; set; }
    float AngleOfPen { get; set; }
    float PenRadialDistanceFromCenter { get; set; }
    float Speed { get; set; }
    bool ToyVisible { get; set; }
    void Clear();
    Color PenColor { get; set; }
  }
  public interface IHypergraphObserver
  {
    IHypergraph Subject {set;}
  }
}

Implementing the Hypergraph Controls Using the Observer Pattern

Listing 2.9 shows the implementation the HypergraphController (see Figure 2.3). This UserControl contains all of the sliders that permit manipulating the Hypergraph instance. To make this interaction orderly, an interface and the Observer pattern is used. Observer exists simply to make it easy to manipulate the state of any object that implements IHypergraph. This is done through binding to delegates. Listing 2.10 shows the implementation of the subject and observer interfaces.

Figure 2.3 The HypergraphController.

Image

Listing 2.9 The Implementation of the HypergraphController

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Hypergraph
{
  public partial class HypergraphController : UserControl, IHypergraphObserver
  {
    public HypergraphController()
    {
      InitializeComponent();
    }

    #region IsubjectObserver Members
    private IHypergraph subject;
    public IHypergraph Subject
  {
    set
    {
      subject = value;
      SubjectChanged();
    }
  }

  private void SubjectChanged()
  {
    if(subject == null ) return;
    TrackBarOuterDisk.Value = (int)subject.OuterRingRadius;
    TrackBarInnerDisk.Value = (int)subject.InnerRingRadius;
    TrackBarAngle.Value = (int)subject.AngleOfPen;
    TrackBarPenRadius.Value = (int)subject.PenRadialDistanceFromCenter;
  }

  #endregion

  private void TrackBarOuterDisk_Scroll(object sender, EventArgs e)
  {
    Changed();
  }

  private void TrackBarInnerDisk_Scroll(object sender, EventArgs e)
  {
    Changed();
  }

  private void TrackBarDrawSpeed_Scroll(object sender, EventArgs e)
  {
    if(timer != null)
      timer.Interval = TrackBarDrawSpeed.Value;
  }

  private Timer timer;
  public Timer Timer
  {
    get
    {
      return timer;
    }
    set
    {
      timer = value;
      if(timer != null)
        TrackBarDrawSpeed.Value = timer.Interval;
    }
  }

  private void TrackBarAngle_Scroll(object sender, EventArgs e)
  {
    Changed();
  }

  private void TrackBarPenRadius_Scroll(object sender, EventArgs e)
  {
    Changed();
  }

  private void CheckBoxShowToy_CheckedChanged(object sender, EventArgs e)
  {
    if(subject!=null)
      subject.ToyVisible = CheckBoxShowToy.Checked;
  }

  private void TrackBarPlotSpeed_Scroll(object sender, EventArgs e)
  {
    Changed();
  }

  private void Changed()
  {
    if(subject == null) return;
    subject.Clear();
    subject.OuterRingRadius = TrackBarOuterDisk.Value;
    toolTip1.SetToolTip(TrackBarInnerDisk, TrackBarInnerDisk.Value.ToString());
    subject.InnerRingRadius = TrackBarInnerDisk.Value;
    toolTip1.SetToolTip(TrackBarInnerDisk, TrackBarInnerDisk.Value.ToString());
    subject.AngleOfPen = TrackBarAngle.Value;
    toolTip1.SetToolTip(TrackBarAngle, TrackBarAngle.Value.ToString());
    subject.PenRadialDistanceFromCenter = TrackBarPenRadius.Value;
    toolTip1.SetToolTip(TrackBarPenRadius, TrackBarPenRadius.Value.ToString());
    subject.Speed = TrackBarDrawSpeed.Value;
    toolTip1.SetToolTip(TrackBarDrawSpeed, TrackBarDrawSpeed.Value.ToString());
  }

  internal void Random()
  {
    Random random = new Random(DateTime.Now.Millisecond);
    TrackBarAngle.Value = random.Next(TrackBarAngle.Minimum,
       TrackBarAngle.Maximum);
    TrackBarInnerDisk.Value = random.Next(TrackBarInnerDisk.Minimum,
      TrackBarInnerDisk.Maximum);
    TrackBarOuterDisk.Value = random.Next(TrackBarOuterDisk.Minimum,
      TrackBarOuterDisk.Maximum);
    TrackBarPenRadius.Value = random.Next(TrackBarPenRadius.Minimum,
      TrackBarPenRadius.Maximum);
    TrackBarPlotSpeed.Value = random.Next(TrackBarPlotSpeed.Minimum,
      TrackBarPlotSpeed.Maximum);
    if(subject != null)
      subject.PenColor = Color.FromArgb(random.Next(255), random.Next(255),
        random.Next(255));
    Changed();
  }
 }
}

Listing 2.10 The Implementation of the Subject and Observer, Which Exist to Make It Easy to Associate a Subject with the Object That Observes It

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Text;

namespace Hypergraph
{
  public interface IHypergraph
  {
    float InnerRingRadius { get; set; }
    float OuterRingRadius { get; set; }
    float AngleOfPen { get; set; }
    float PenRadialDistanceFromCenter { get; set; }
    float Speed { get; set; }
    bool ToyVisible { get; set; }
    void Clear();
    Color PenColor { get; set; }
  }

  public interface IHypergraphObserver
  {
    IHypergraph Subject {set;}
  }
}

Using Conversion Operators

A very common task is converting data from one form to another. Historically, this entailed creating the target object and copying data from the source argument and initializing new instances of the target object. This is necessary but tedious (and time-consuming) work and converting code is prevalent.

Prevalent, tedious, and time-consuming work cries out for convenience tools. The new version of .NET has answered this cry. The following sections explore the conversion operators ToArray, OfType, Cast, AsEnumerable, ToList, ToDictionary, and ToLookup.

ToArray

Arrays are fast but not very convenient. It is cumbersome to write array management code when collections like List<T> make managing data easier. However, many legacy APIs, existing code, and some algorithms are designed to use or are locked into arrays. A perfect example is the Hypergraph toy. It is much easier to store points in a collection, but GDI+ methods like DrawCurve are designed to use arrays. To get the ease of use of collections but use an array when drawing the arcs, you can call ToArray on the List of ColoredPoints.

The elided class fragments in Listing 2.11 shows the complete GetPoints method, which uses a LINQ query and ToArray and the SaveToyToFile method. The code uses the GetPoints method to convert the list of points to an array and SaveToyToFile uses a bitmap and the points to save the image to an external file. For completeness, the Broadcaster—which uses the Observer form of publish subscribe, or what is sometimes called the broadcast-listener—pattern is provided in Listing 2.12.

Listing 2.11 The Combination of a LINQ Query, the ToArray Conversion Method, and GDI+ to Save an Image to a File

  public class ColoredPointList : List<ColoredPoint>
  {

    /// <summary>
    /// Use conversion operator ToArray
    /// </summary>
    /// <returns></returns>
    public PointF[] GetPoints()
    {

       var points = from p in this select p.MyPoint;
       return points.ToArray<PointF>();
       //return (from p in this select p.MyPoint).ToArray<PointF>();
       //return (from p in this select p.MyPoint).ToArray<PointF[]>();

       //PointF[] points = new PointF[this.Count];
       //for(int i=0; i<this.Count; i++)
       // points[i] = this[i].MyPoint;

       //return points;
     }

}
Public class Hypergraph
{
public PointF[] Points
    {
      get {  return list.GetPoints(); }
    }
public void SaveToyToFile(string filename)
    {
      Broadcaster.Broadcast(“Saving to {0}”, filename);
      Hypergraph copy = this.Clone();
      foreach (var p in copy.List)
      {
        p.MyPoint = new PointF(p.MyPoint.X - copy.CenterX +
          (copy.OuterRingRadius + copy.InnerRingRadius) / 2,
          p.MyPoint.Y - copy.CenterY +
          (copy.OuterRingRadius + copy.InnerRingRadius) / 2);
      }

      GraphicsPath path = copy.Path;
      RectangleF bounds = path.GetBounds();
      Bitmap bitmap = new Bitmap((int)bounds.Width, (int)bounds.Height);
      Graphics graphics = Graphics.FromImage(bitmap);
      graphics.DrawCurve(MyPen, copy.Points);
      bitmap.Save(filename, ImageFormat.Jpeg);
      Broadcaster.Broadcast(“Saved”);
    }

}

Listing 2.12 The Broadcast-Listener Behavior Pattern, an Implementation of the Observer Pattern Is Used to Move Messages from Anywhere in the System to the Presentation Layer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Hypergraph
{
  public interface IListener
  {
    void Listen(string message);
    bool Listening { get; }
  }

  public class Broadcaster
  {
    private static List<IListener> listeners = new List<IListener>();
    public static void Add(IListener listener)
    {
      listeners.Add(listener);
    }

    public static void Broadcast(string message)
    {
      foreach(var listener in listeners)
        listener.Listen(message);
    }

    public static void Broadcast(string format, params object[] o)
    {
      Broadcast(string.Format(format, o));
    }
  }
}

For example, if any class implements IListener, then registered ListenersIListeners added to the Broadcasters internal list—will receive status messages from anywhere in the system that broadcasts a message. In the Hypergraph example, the main Form implements IListener and uses the string messages to update the Form’s StatusBar.

OfType

The conversion operator OfType<T> returns an IEnumerable<T> collection of only the types defined by the parameter T. For example, initializing a list of objects where some are integers, you can quickly extract just the integers, as demonstrated in Listing 2.13. Listing 2.13 returns an IEnumerable<int> with 1 and 4 in the resultset.

Listing 2.13 The OfType<T> Conversion Operator Quickly Extracts Only Elements Whose Type Matches the Template Parameter T’s Type

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OfTypeDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var numbers = new object[]{1, “two”, null, “3”, 4};
      foreach(var anInt in numbers.OfType<int>())
        Console.WriteLine(anInt);
      Console.ReadLine();
    }
  }
}

Cast

To receive an exception if elements of the source type cannot be converted to the target type, use the Cast <T> conversion operator. If you use numbers from Listing 2.13, you’ll receive an InvalidCastException when the Cast operator hits the string “two” (see Listing 2.14).

Listing 2.14 Use Cast<T> When You Want to Receive an InvalidCastException If Elements of the Source Do Not Match the typeof T

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CastDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      var numbers = new object[]{1, “two”, null, “3”, 4};
      foreach(var anInt in numbers.Cast<int>())
        Console.WriteLine(anInt);
      Console.ReadLine();

    }
  }
}

AsEnumerable

Many types implement IEnumerable. Some of these types implement public members that are identical to IEnumerable. For instance, if you have a type MyList that implements a Where method (as does IEnumerable<T>), invoking Where on MyList would call MyList’s implementation of Where. By calling the AsEnumerable method first and then calling Where, you invoke IEnumerable’s Where method instead of MyList’s (see Listing 2.15).

Listing 2.15 AsEnumerable Forces an Object That Implements IEnumerable to Use the Behaviors of the IEnumerable Interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AsEnumerable
{
  class Program
  {
    static void Main(string[] args)
    {
      const string Bacon = “For it is the solecism of a Prince “ + 
        “to think to control the end yet not endure the mean.”;

      MyList<string> strings = new MyList<string>();
      strings.AddRange(Bacon.Split());
      IEnumerable<string> inMyList = strings.Where(str=>str==“solecism”);

     foreach(string s in inMyList)
       Console.WriteLine(s);

      Console.ReadLine();

      IEnumerable<string> notInMyList =
        strings.AsEnumerable().Where(str=>str==“Prince”);

      foreach(string s in notInMyList)
        Console.WriteLine(s);
      Console.ReadLine();

    }
  }

  public class MyList<T> : List<T>
  {
    public IEnumerable<T> Where(Func<T, bool> predicate)
    {
      Console.WriteLine(“MyList”);
      return Enumerable.Where(this, predicate);
    }
  }
}

In Listing 2.15, the first Where call invokes MyList’s Where. After the call to AsEnumerable(), the next call is to List<T>’s Where as part of List<T>’s implementation of IEnumerable.

ToList

The ToList conversion operator forces an immediate query evaluation and stores the results in a List<T>. Listing 2.16 shows an anonymous type of college football teams, a LINQ query whose results are converted to a List<T>, and some additional teams are added.

Listing 2.16 Converting Query Results to a List<T> Lets Us Add Additional Elements to the Resultset

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ToListDemo
{
 class Program
 {
   static void Main(string[] args)
   {
     var teams = new string[]{“Spartans”, “Fighting Irish”, “Wolverines”};
     List<string> footballTeams = (from name in teams select name). 
     →ToList<string>();
     footballTeams.Add(“Hoosiers”);
     footballTeams.Add(“Fighting Illini”);
     footballTeams.Add(“Badgers”);
     Console.WriteLine(footballTeams.Count);
     Console.ReadLine();
    }
  }
}

ToDictionary

A dictionary is a collection of name and value pairs. ToDictionary converts an IEnumerable<T> object—such as is returned from a LINQ query—into an IDictionary<Key, Value> object. The ToDictionary method used in Listing 2.17 uses a selector Func<Game, string> to set the keys. The selector used is the Lambda Expression Key=>Key.Opponent, which essentially makes the Opponent name the key and the Game object itself the value part of the pair. Key is an instance of Game and Opponent is the string part of the Func generic. (For more information on Lambda Expressions, see Chapter 5, “Understanding Lambda Expressions and Closures.”)

Listing 2.17 Converting a List<T> to an IDictionary<K,V>, a Dictionary of Name and Value Pairs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ToDictionaryDemo
{
  class Program
  {
    public class Game
    {
      public string Opponent{get; set; }
      public string Score{get; set; }
  }

  static void Main(string[] args)
  {
    var spartans = new List<Game>{
      new Game{Opponent=“UAB”, Score=“55-18”},
      new Game{Opponent=“Bowling Green”, Score=“28-17”},
      new Game{Opponent=“Pittsburgh”, Score=“17-13”},
      new Game{Opponent=“Notre Dame”, Score=“17-14”}};

    //var games = from g in spartans select g;

    IDictionary<string, Game> stats = spartans
      .ToDictionary(Key=>Key.Opponent);
    Console.WriteLine(“Spartans vs. {0} {1}”, stats[“Notre Dame”].Opponent,
       stats[“Notre Dame”].Score);
     Console.ReadLine();
    }
  }
}

ToLookup

ToLookup converts an IEnumerable<T> to a Lookup<Key, Element> type. Lookup is like a dictionary, but where a Dictionary uses a single key value, Lookup maps keys to a collection of values. Lookups have no public constructor and are immutable. You cannot add or remove elements or keys after they are created.

Listing 2.18 defines a Product class containing a Code and Description—in a real application, you might use SKU, or stock-keeping unit. Next, we request a Lookup<Key, Element> from the List of Products—List<Product>. The key for the Lookup is defined by the Lambda Expression c=>c.Substring(0,3), which keys the Lookup on the first three characters of the Product.Code. Finally, the Lookup values are displayed in groups by the key using the IGrouping interface, and the last bit of code shows how to get just one group by the key.

Listing 2.18 Using the Lookup<Key, Element> Type and the IGrouping Interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ToLookupDemo
{
 class Program
 {
   public class Product
   {
     public string Code{ get; set; }
     public string Description{ get; set; }
   }

   static void Main(string[] args)
   {
     List<Product> products = new List<Product>
     {
       new Product{Code=“SPM1”, Description=“Spam”},
       new Product{Code=“SPM2”, Description=“Mechanically Separated Chicken”},
       new Product{Code=“LME1”, Description=“Bologna”},
       new Product{Code=“SUP1”, Description=“Tomato”},
       new Product{Code=“SUP2”, Description=“Chicken Noodle”}};

       Lookup<string, string> lookup = (Lookup<string,string>)
         products.ToLookup(
         c=>c.Code.Substring(0,3), c=>c.Code + “ ” + c.Description);

       foreach(IGrouping<string, string> group in lookup)
       {
         Console.WriteLine(group.Key);
         foreach(string s in group)
           Console.WriteLine(“  {0}”, s);
       }

       Console.ReadLine();

       IEnumerable<string> spmGroup = lookup[“SPM”];
       foreach(var str in spmGroup)
         Console.WriteLine(str);

       Console.ReadLine();
    }
  }
}

Summary

Compound type initialization makes it easier to initialize arrays and lists and is an essential capability for initializing anonymous types. Conversion operators take some of the tedium out of converting between convenient types and other types. More important, this chapter demonstrates the classic onion layering of complexity in .NET that makes it possible to do a lot of work with relatively few lines of code.

This chapter also introduced or demonstrated and provided additional examples of Lambda Expressions, LINQ queries, conversion operators, the Observer pattern, and provided some examples that used GDI+. Lambda Expressions are discussed in detail in Chapter 5, “Understanding Lambda Expressions and Closures,” and LINQ queries are explored in Chapter 6, “Using Standard Query Operators.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.198.94