.NET – Tạo instance và chuyển đổi kiểu dữ liệu bất kì

Trong bài viết này sẽ giới thiệu cách giải quyết hai vấn đề bạn có thể gặp phải khi làm việc với .NET:
– Tạo instance của một kiểu dữ liệu bất kì với .
– Chuyển đổi một đối tượng sang một kiểu dữ liệu bất kì.

Tạo instance của một kiểu dữ liệu bất kì.

Việc tạo instance của một kiểu dữ liệu chỉ cần sử dụng phương thức Activator.CreateInstance(). Tuy nhiên đối với các kiểu dữ liệu không chứa parameterless constructor (phương thức khởi tạo không có tham số) thì phương thức này sẽ ném ra ngoại lệ MissingMethodException “No parameterless constructor defined for this object”.

Để giải quyết, tôi sẽ thay thế phương thứcc này bằng phương thức FormatterServices.GetUninitializedObject(). Tuy nhiên, phương thức vẫn không thể tạo được instance của kiểu string, vì vậy thay vì dùng reflection, tôi phải giải quyết bằng cách sau:

public static T CreateInstance<T>()
{
    var type = typeof(T);
    if (type == typeof(string))
    {
        return (T)(object)String.Empty;
    }
    else
    {
        return (T)FormatterServices.GetUninitializedObject(type);
    }
}

Chuyển đổi đối tượng sang kiểu dữ liệu bất kì

Với các kiểu dữ liệu nguyên thủy (primitive type), bạn có thể dùng phương thức Convert.ChangeType() để chuyển đổi. Với kiểu dữ liệu phức tạp, cách linh hoạt nhất là sử dụng reflection để lặp qua các property của đối tượng nguồn và gán cho property tương ứng của đối tượng đích.

public static TDest ChangeType<TDest>(object source)
{
    var destObj = CreateInstance<TDest>();

    var sourceType = source.GetType();
    var destType = typeof(TDest);

    if (sourceType.IsValueType || destType.IsValueType)
        return (TDest)Convert.ChangeType(source, destType);

    var properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

    foreach (var prop1 in properties)
    {

        var prop2 = destType.GetProperty(prop1.Name);

		// destType does not has a property named prop1.Name
        if (prop2 == null)
            continue;

        var value = prop1.GetValue(source, null);

		// prop1 and prop2 are not same type
        if (prop1.PropertyType != prop2.PropertyType)
            continue;

        prop2.SetValue(destObj, value, null);
    }
    return destObj;
}

Trường hợp viết dưới dạng extension method với tên ToType():

static class Y2Extensions
{
    public static T CreateInstance<T>()
    {
        var type = typeof(T);
        if (type == typeof(string))
        {
            return (T)(object)String.Empty;
        }
        else
        {
            return (T)FormatterServices.GetUninitializedObject(type);
        }
    }

    public static TDest ToType<TDest>(this object source)
    {
        var destObj = CreateInstance<TDest>();

        var sourceType = source.GetType();
        var destType = typeof(TDest);

        if (sourceType.IsValueType || destType.IsValueType)
            return (TDest)Convert.ChangeType(source, destType);

        var properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop1 in properties)
        {

            var prop2 = destType.GetProperty(prop1.Name);

            // destType does not has a property named prop1.Name
            if (prop2 == null)
                continue;

            var value = prop1.GetValue(source, null);

            // prop1 and prop2 are not same type
            if (prop1.PropertyType != prop2.PropertyType)
                continue;

            prop2.SetValue(destObj, value, null);
        }
        return destObj;
    }

}

Kiểm tra kết quả

Để kiểm tra, tôi tạo hai class Foo và Bar sau:

class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreateDate { get; set; }
}

class Bar
{
    public int Id { get; set; }
    public string Name { get; set; }

    public string CreateDate { get; set; }

	// This class doest not has a parameterless constructor
	public Bar(int id) { }

    public override string ToString()
    {
        return String.Format("Bar: {0} - {1} - {2}",
            Id, Name, String.IsNullOrEmpty(CreateDate)?"N/A":CreateDate);
    }
}

Và phương thức Main():

static void Main(string[] args)
{
    Console.WriteLine("1111".ToType<int>()* 2);  // 2222

    var foo = new Foo { Id = 1, Name = "Foo1", CreateDate = DateTime.Today };

    var bar = foo.ToType<string>();

    Console.WriteLine(bar); // Bar: 1 - Foo1 - N/A

    Console.Read();
}

Output:

2222
Bar: 1 - Foo1 - N/A

Theo YinYang’s Programming Blog