Wednesday, January 18, 2006
C# User-defined conversions
In a related series of articles, I described how to pass custom classes between a web service and a web service client. One of the tools available for aiding in the process is to use user-defined conversions. This is an often overlooked feature of C# that deserves better exposure. For the sake of this article, I will define user-defined conversions, as does the C# specification, to mean conversions created by using the explicit or the implicit C# keywords.There are two types of user-defined conversions: explicit and implicit. The two types work just as you would expect based on the definition of the terms. Explicit conversions must be explicitly requested while in the case of implicit conversions, the request is implied and is performed without explicit instructions. You have used both, no doubt.
To explain explicit and implicit conversions, let's look at an example you have probably already used in your code:
int i = 5;This is an example of an implicit conversion. We didn't have to explicity instruct the framework to convert from an integer to a decimal. Our intent was implied and the framework understood how to carry out our intent so it just did it.
decimal d = i;
// d = 5
In another example
decimal d = 5;In this case, the implied conversion could not be performed. A decimal type loses precision when converting to an integer. A rule of implicit conversions is that they should only be performed when no data or precision will be lost by the conversion.
int i = d;
// Compiler error!
We can get around this rule by explicity instructing the compiler to make the conversion, eliminating any doubt about what we wanted to do:
decimal d = 5;This time, because of the explicit conversion command, better known as an explicit cast, the compiler realizes that we understand that d is not an integer but that we want to perform the conversion anyway. It knows how to do the conversion so it follows orders.
int i = (int) d;
// i = 5
For implicit or explicit conversions to work, they have to be pre-defined. The examples above demonstrate that there is a pre-defined (in the framework) implicit conversion from an integer to a decimal. There is also a pre-defined explicit conversion from a decimal to an integer. There is not a pre-defined implicit conversion from a decimal to an integer and that's why the second example results in a compiler error.
Creating your own conversions
You can create your own conversions for any class you create in your own code. In the demonstration for passing objects between web services and clients, there is a NewsService.NewsItemData class, where NewsService is the name of the web service class, that the web services returns to the client or accepts as a WebMethod parameter from the client:
// A NewsItemData for passing between the web service and the clientIn the client, there is another, almost identical, class that also has a
public class NewsItemData
{
private int _NewsId;
private string _NewsTitle;
private string _NewsDate;
private string _PicName;
private string _NewsSummary;
private Reporter[] reporters;
public int NewsId
{
get{ return _NewsId; }
set{ _NewsId = value; }
}
public string NewsTitle
{
get{ return _NewsTitle; }
set{ _NewsTitle = value; }
}
public string NewsDate
{
get{ return _NewsDate; }
set{ _NewsDate = value; }
}
public string PicName
{
get{ return _PicName; }
set{ _PicName = value; }
}
public string NewsSummary
{
get{ return _NewsSummary; }
set{ _NewsSummary = value; }
}
public Reporter[] Reporters
{
get { return reporters; }
set { reporters = value; }
}
}
Visible property and a FormatToHtmlString method:
// A NewsItem for use in the clientIn the NewsItem class, there is a property and a method not in the NewsItemData class. It is the property that concerns us here. The guidelines for user-defined conversions state that if the conversion causes the loss of data, the conversion should be marked explicit to avoid unforeseen consequences. Because I lose the Display property when we convert from a newsItem to a NewsItemData, the conversion should be an explicit one:
public class NewsItem
{
private int _NewsId;
private string _NewsTitle;
private string _NewsDate;
private string _PicName;
private string _NewsSummary;
private bool _Visible;
private Reporter[] _Reporters;
public int NewsId
{
get{ return _NewsId; }
set{ _NewsId = value; }
}
public string NewsTitle
{
get{ return _NewsTitle; }
set{ _NewsTitle = value; }
}
public string NewsDate
{
get{ return _NewsDate; }
set{ _NewsDate = value; }
}
public string PicName
{
get{ return _PicName; }
set{ _PicName = value; }
}
public string NewsSummary
{
get{ return _NewsSummary; }
set{ _NewsSummary = value; }
}
public bool Visible
{
get{ return _Visible; }
set{ _Visible = value; }
}
public Reporter[] Reporters
{
get { return _Reporters; }
set { _Reporters = value; }
}
public string FormatToHtmlString()
{
return "<B>" + _NewsSummary + "</B>";
}
}
One rule of user-defined conversions is that the conversion must convert either to or from the enclosing type. For instance, if we create the conversion method in the NewsItem class, we can create a user-defined conversion from a NewsItem to another type, such as the NewsItemData, or to a NewsItem from another class such as, again, the NewsItemData class. We can put the code for conversions for either direction into either of the two classes involved in the conversion.
In my example of a web service and web service client, the conversion between NewsItem and NewsService.NewsItemData would have to be in the client side NewsItem class. The web service doesn't know anything about the client but the client has a reference to the web service and is aware of the NewsItemData class and is, therefore, able to do the conversion. The following methods, then, goes in the client's NewsItem class:
public static explicit operator NewsService.NewsItemData(NewsItem item)and
{
NewsService.NewsItemData newItem = new NewsService.NewsItemData();
newItem.NewsId = item.NewsId;
newItem.NewsTitle = item.NewsTitle;
newItem.NewsDate = item.NewsDate;
newItem.PicName = item.PicName;
newItem.NewsSummary = item.NewsSummary;
newItem.Reporters = new Reporters[item.Reporters.Length];
for (int count = 0; count < item.Reporters.Length; count++)
{
newItem.Reporters[count] = itemReporters[count];
}
return newItem;
}
public static explicit operator NewsItem(NewsService.NewsItemData item)
{
NewsItem newItem = new NewsItem();
newItem.NewsId = item.NewsId;
newItem.NewsTitle = item.NewsTitle;
newItem.NewsDate = item.NewsDate;
newItem.PicName = item.PicName;
newItem.NewsSummary = item.NewsSummary;
newItem.Reporters = new Reporters[item.Reporters.Length];
for (int count = 0; count < item.Reporters.Length; count++)
{
newItem.Reporters[count] = itemReporters[count];
}
return newItem;
}
The first operator converts from a NewsItem to a NewsService.NewsItemData. The second operator performs the opposite conversion, from a NewsItemData to a NewsItem. Notice that to convert the Reporters array, we had to loop through the array elements. This is because user-defined conversions must convert to or from the type of the enclosing class. There is no editable "Reporter[]" class to put a user-defined conversion in to, so user-defined conversions will not work to convert from an array type to another array type.
Also, because the NewsItemData class uses a NewsService.Reporter array and the client uses a Reporter array based on its own namespace, we have to create a user-defined conversion between the two Reporter classes in the client's Reporter class. In the series of articles about passing objects to and from web services, the Reporter class extends the Person class.
public static implicit operator NewsService.Reporter(Reporter reporter)Because the user-defined conversion from Reporter to NewsService.Reporter was marked with the implicit keyword, when we converted the Reporter arrays in the NewsItem class, we did not have to cast from Reporter to NewsService.Reporter as in:
{
NewsService.Reporter newReporter = new NewsService.Reporter();
newReporter.Name = reporter.Name;
newReporter.Age = reporter.Age;
newReporter.City = reporter.City;
newReporter.Agency = reporter.Agency;
return newReporter;
}
newItem.Reporters[count] = (NewsService.Reporter)itemReporters[count];but we could have use the cast operator. Any existing implicit conversion, either system-defined or user-defined, can be called explicitly even though doing so might be thought of as redundant. It is often useful to explicitly call implicit conversions to make your code more clear.
Using the conversions
The conversions we created above are useful for passing client objects to the web service. For each of these examples, assume the following code:
// Create the connection to the web service proxyIf the web service has a WebMethod with the signature:
WebServiceReturnTypeDemo.NewsService.NewsService news = new WebServiceReturnTypeDemo.NewsService.NewsService();
// Set security for the web service - may not be required in all environments
news.Credentials = System.Net.CredentialCache.DefaultCredentials;
public bool Save(NewsService.NewsItemData)and we call the method without our user-defined explicit conversion, assuming our client application is in a namespace named ClientApp, we will get an exception stating that the system cannot convert from NewsService.NewsItemData to ClientApp.NewsItem. If we call the method with the client NewsItem using the explicit conversion that we defined:NewsItem item = new NewsItem();the conversion works. We have, for all intents and purposes, passed our client side NewsItem to the server.
// Some code here to populate the properties of item and then:
bool result = news.Save((NewsService.NewsItemData)item);
If the web service has a WebMethod with the signature:
public bool Save(NewsService.Reporter)We can call the method with the client Reporter using the implicit conversion that we defined:
Reporter reporter = new Reporter();It works even though we didn't cast reporter as a NewsService.Reporter. That's about as simple as it gets. Well, almost as simple as it gets. If you want really simple, read on.
// Some code here to populate the properties of reporter and then:
bool result = news.Save(reporter);
Earlier, I said that if a conversion loses data it should be marked with the explicit keyword. I also said that converting from the client NewsItem to NewsService.NewsItemData loses data because we lost the Display property that exlists in NewsItem but not in NewsService.NewsItemData and therefore, the conversion should be explicit. But I didn't say must be explicit.
In this example, the web service doesn't care about the Display property and we know that it will never care about the Display property. So was anything really lost? The only function of the NewsService.NewsItemData class is to pass data to and from the web service and the web service doesn't care about the Display property. So the answer is no, nothing was lost. We can safely replace the explicit keyword on our NewsItem conversion created earlier with the implicit keyword:
public static implicit operator NewsService.NewsItemData(NewsItem item)and then, when we call the Save method,NewsItem item = new NewsItem();the method succeeds even though we passed it a ClientApp.NewsItem while its signature called for a NewsService.NewsItem.
// Some code here to populate the properties of item and then:
bool result = news.Save(item);
Conclusion
Talk about the ultimate in simplicity. Assuming we have created equivelent conversions on the web service side to go to and from the data-only NewsItemData class to the NewsService.NewsItem class, then now, on both sides of the wire, we write all of our code without ever thinking about conversion.
On the client side, we always work with NewsItem objects and on the web service side we always work with the NewsItem objects. Do we care that in between there was a conversion to and from a NewsItemData class that we never touch or think about after creating our original conversion operators? No, we don't care. For the purposes of our code, the two NewsItem classes have just become one and the same.



