With this third article, I'm closing my introductory series to the world of Extension Methods and XNA.
At first, this article was meant to focus on how to extend the Random class to get new colors, but Ziggy, in his article entitled "Particles Using LINQ" -worth reading, btw- has clearly shown how to achieve this so I will take that great article as a starting point to go a bit deeper in the discussion on the matter.
If you see Ziggy's method named "RandColor" -which from now on I will refer to it as "NextColor" to be in line with the Random class, he's showing one of the new features of the XNA Framework 3: when using floats there's no need to first create a Vector3 in order to then create a Color, anymore.
In previous versions of XNA, to use floats in order to create a color, you would have declared:
public static Color NextColor(this Random rand)
{
return new Color(new Vector3(rand.NextFloat(), rand.NextFloat(), rand.NextFloat()));
}
But from the XNA Framework 3 and on, this also works:
public static Color NextColor(this Random rand)
{
return new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}
Now, if you see the Particle2D class in the above-mentioned article, you will notice the field named "Color", which is declared as an variable in uppercase; a new extension method could be created for it, as follows:
public static void NextColor(this Random rand, out Color color)
{
color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}
Thus, we can use it within the Update method in the referred example to replace this line:
p.Color = rand.NextColor();
By this new line:
rand.NextColor(out p.Color);
Since the type Color is a struct, the advantage of using the latter over the former could be generally deemed as marginal, but eventually you may get to the situation when even the slightest improvement in your code is desired, specially when you're targeting the compact framework.
New question: what if you followed the design guidelines of C#, declaring the color field in lowercase as a private field and instead, a public getter and setter was included in that class, in the form of a public property named "Color".
In that case, the new overloaded method cannot be used since Properties and Indexers cannot be passed as out/ref parameters. Why? Well, just remember that when you're using properties and indexers you're not directly dealing with variables but methods. In that particular case, the first implementation -that is, the one included in Ziggy's article- is the best solution.
Let's face it! You could be tempted to pass the existing instance of Particle2D as "ref" to sort out the above-mentioned constraint, as follows:
public static void NextColor(this Random rand, ref Particle2D particle)
{
particle.Color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}
It should compile ok BUT ONLY if you're NOT PASSING to that method the temporary variable created by a foreach loop -as opposed to the base example. This operation is just NOT ALLOWED for the iteration variables of foreach loops. My advice here is simple: avoid this tempting implementation and as I said before just use the one presented by Ziggy. Generally, the simplest solution is the correct one.
One side note: in case you're new to C#, it's important to notice that in this case we are using "ref" instead of "out", since we need an existing (that is, a non-null) instance of the class in order to assign the new color. Otherwise, by using "out" we should have to create an instance of type Particle2D from within the extension method BEFORE assigning the new color to it.
Now, let's move onto an interesting situation. What if:
- We are not and will be not inside a foreach loop,
- We don't want to expose the whole Particle2D to the extension method, and at the same time
- We need to use this method for as many classes that have declared setter properties for colors?
If that is the case, then you should take a look to this implementation:
public interface IColor
{
Color Color { get; set; }
}
public static class HelperMethods
{
...
public static void NextColor<T>(this Random rand, ref T instance)
where T : class, IColor
{
instance.Color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}
}
As you can see, we are hereby combining the power of Generics with Extension Methods.
But, why the "IColor" constraint? What we are saying here is simple: only types that implement the Color property can call this extension method. And those non-null instances passed to the method as a parameter (remember we are using the "ref" word here) will be handled as IColor types.
Ok, but why the additional "class" constraint? Tricky one. As a corollary of the previous constraint, if we do not specify that only classes are accepted as parameters, then structs can be also passed when calling the method, and for that they will have to implement the IColor interface ... Meaning? ... Tic tac tic tac ... Yeap, boxing. Ugh! That nasty word. Handling structs through interfaces causes boxing. Chilly, isn't it? Now you know it, in case you didn't.
To sum up, as you can see Extension Methods are quite handy, but you have to use them with care if you want to avoid getting into a design trap. As usual, not all the situations can be solved the same way, even though at first it sounds logic to you. You may find your-self saying "Whaaat? Buy whyyy? ...". So, I hope these examples show you what to use and when and what to avoid and why.
Some final thoughts about Extension Methods:
- They are great for usual tasks: believe it or not, two common types where I always find my-self repeating usual tasks are TYPE and RANDOM classes, but this feature can be used with as many types as you need,
- They are ituitive to use: before this functionality was introduced to the .NET Framework, one usually used to write a static class filled with helpers; please don't misundertsand me, you still have, but now those helpers are presented in a more intuitive and user-friendly way. With Extension Methods, you don't have to look for the methods "by browsing" the static class were you declared and implementem them; instead, you just look on the instances of the types you are using. And last but not least,
- They comply with design principles: as said in my first article in the series, we are not breaking any design principles, since we have no direct access to private members of the "extended" types.
In short, if you use Extension Methods with care and wisdom, they can be a true friend at the time of coding your XNA-based game or application: they simply help you extend any types with new methods AS IF these where originally part of those types, what in turn can be handy, for instance:
- When you don't want to create a specification of a class, or on the other hand,
- When you want to put more functionality into a sealed class somehow -to some extent, of course.
So go ahead, try Extension Methods and share what you find with the XNA Community. It would be great and fun to know what you are using them for!
Well, I hope you have found this series useful. Constructive comments and suggestions are always welcome.
Cheers!
~Pete
Hey, nice articles. I´m not sure but there´s a chance that the by-reference version of the RandColor has more overhead than the original because of the boxing/unboxing of the color (a valueType). Just a guess though.
ReplyDeleteExcellent question. And the answer lays here:
ReplyDeletehttp://msdn.microsoft.com/en-us/library/14akc2c7.aspx
"Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not related; a method parameter can be modified by ref regardless of whether it is a value type or a reference type. Therefore, there is no boxing of a value type when it is passed by reference."
Plus, for large value types passing by ref is preferred on the compact framework:
http://blogs.msdn.com/netcfteam/archive/2006/12/22/managed-code-performance-on-xbox-360-for-xna-part-2-gc-and-tools.aspx