HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMajor

TryCast<T> method

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
methodtrycaststackoverflow

Problem

This isn't urgent, it is more along the lines of trivia or a challenge. The solution works fine as it is, but I suspect it could be better.

What follows is a method I came up with a while back in a rather ugly situation where I need to make a "best effort" to try casting an object of an unrestricted unknown type TO an unrestricted unknown type. The code has just been bugging me. It seems that there should be a more elegant way to do this, but I wanted to get that "Best Effort" part right.

The methods follows the "Try" convention. It accepts an object "value" and an out param of type T, "result." It attempts to cast value into result as type T. If it succeeds, it returns true. If it cannot, it sets result = default(T) and returns false.

It feels like I'm going to lots of trouble in the method. I'd be open to suggestions to streamline this a bit. Comments included here are mostly not in the original... just to explain why a few things were done the way they were done.

```
///
/// Tries to cast to an instance of type .
///
/// The type of the instance to return.
/// The value to cast.
/// When this method returns true, contains cast as an instance of . When the method returns false, contains default(T).
/// True if is an instance of type ; otherwise, false.
public static bool TryCast(this object value, out T result)
{
var destinationType = typeof(T);
var inputIsNull = (value == null || value == DBNull.Value);

/*
* If the given value is null, we'd normally set result to null and be done with it.
* HOWEVER, if T is not a nullable type, then we can't REALLY cast null to that type, so
* TryCast should return false.
*/
if (inputIsNull)
{
// If T is nullable, this will result in a null value in result.
// Otherwise this will result in a default instance in result.
result = default(T);

// If the input is null and T is nullable, we report success. Otherwise we report failu

Solution

For casting, I have a much simpler method in mind:

public static bool TryCast(this object obj, out T result)
{
    if (obj is T)
    {
        result = (T)obj;
        return true;
    }

    result = default(T);
    return false;
}


You don't need to detect nullable types manually since is operator already checks for it: 5 is int? returns true, so the following code writes 5 to the console.

int value = 5;
int? result;
if (value.TryCast(out result))
    Console.WriteLine(result);


The following writes nothing because TryCast returns false.

string value = "5";
int? test;
if (value.TryCast(out test))
    Console.WriteLine(test);


Lastly, the following should write two lines that are "test 1" and "test 2".

var list = new List();
list.Add("test 1");
list.Add("test 2");

IEnumerable enumerable;
if (list.TryCast(out enumerable))
    foreach (var item in enumerable)
        Console.WriteLine(item);


I'm really against this approach:

if (underlyingType == typeof(Guid))
{
    if (value is string)
    {
        value = new Guid(value as string);
    }
    else if (value is byte[])
    {
        value = new Guid(value as byte[]);
    }

    //...


If you want this kind of custom conversion features, my suggestion would be to keep your converters in a static, thread-safe Converter collection. A sample converter class can be like this:

public abstract class Converter
{
    private readonly Type from; // Type of the instance to convert.
    private readonly Type to;   // Type that the instance will be converted to.

    // Internal, because we'll provide the only implementation...
    // ...that's also why we don't check if the arguments are null.
    internal Converter(Type from, Type to)
    {
        this.from = from;
        this.to = to;
    }

    public Type From { get { return this.from; } }
    public Type To { get { return this.to; } }

    public abstract object Convert(object obj);
}


And the implementation is:

// Sealed, because this is meant to be the only implementation.
public sealed class Converter : Converter
{
    Func converter; // Converter is strongly typed.

    public Converter(Func converter)
        : base(typeof(TFrom), typeof(TTo)) // Can't send null types to the base.
    {
        if (converter == null)
            throw new ArgumentNullException("converter", "Converter must not be null.");

        this.converter = converter;
    }

    public override object Convert(object obj)
    {
        if (!(obj is TFrom))
        {
            var msg = string.Format("Object is not of the type {0}.", this.From.FullName);
            throw new ArgumentException(msg, "obj");
        }

        // Can throw exception, it's ok.
        return this.converter.Invoke((TFrom)obj);
    }
}


To initialize it:

var int32ToString = new Converter(i => i.ToString());
var stringToInt32 = new Converter(s => int.Parse(s));

// Converters should be a thread-safe collection of our abstract `Converter` type.
Converters.Add(int32ToString);
Converters.Add(stringToInt32);


With support for the custom converters, the final TryCast method becomes this:

public static bool TryCast(this object obj, out T result)
{
    if (obj is T)
    {
        result = (T)obj;
        return true;
    }

    // If it's null, we can't get the type.
    if (obj != null)
    {
        var converter = Converters.FirstOrDefault(c =>
            c.From == obj.GetType() && c.To == typeof(T));

        // Use the converter if there is one.
        if (converter != null)
            try
            {
                result = (T)converter.Convert(obj);
                return true;
            }
            catch (Exception)
            {
                // Ignore - "Try*" methods don't throw exceptions.
            }
    }

    result = default(T);
    return false;
}

Code Snippets

public static bool TryCast<T>(this object obj, out T result)
{
    if (obj is T)
    {
        result = (T)obj;
        return true;
    }

    result = default(T);
    return false;
}
int value = 5;
int? result;
if (value.TryCast(out result))
    Console.WriteLine(result);
string value = "5";
int? test;
if (value.TryCast(out test))
    Console.WriteLine(test);
var list = new List<string>();
list.Add("test 1");
list.Add("test 2");

IEnumerable<string> enumerable;
if (list.TryCast(out enumerable))
    foreach (var item in enumerable)
        Console.WriteLine(item);
if (underlyingType == typeof(Guid))
{
    if (value is string)
    {
        value = new Guid(value as string);
    }
    else if (value is byte[])
    {
        value = new Guid(value as byte[]);
    }

    //...

Context

StackExchange Code Review Q#17982, answer score: 30

Revisions (0)

No revisions yet.