In This Chapter
Initializing Objects with Named Types
Initializing Anonymous Types
Initializing Collections
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.
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.)
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);
}
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);
}
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.
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
.)
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.
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.
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
.
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.
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.
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.
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;
}
}
}
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.”
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.
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();
}
}
}
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;}
}
}
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.
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();
}
}
}
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;}
}
}
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.
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”);
}
…
}
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 Listeners
—IListeners
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.
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();
}
}
}
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).
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).
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.
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.”)
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.
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();
}
}
}
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.”
18.188.198.94