Truyền một biến bằng cách sử dụng biến Loại


280

Trong C # tôi có thể truyền một biến đối tượng loại thành một biến loại T trong đó T được xác định trong biến Loại không?


12
Không hoàn toàn đúng chủ đề, nhưng bạn có vẻ mờ nhạt về "diễn viên" nghĩa là gì có thể là một ý tưởng tốt để hiểu chính xác mục đích và ngữ nghĩa của nhà điều hành diễn viên là gì. Đây là một khởi đầu tốt: blog.msdn.com/ericlippert/archive/2009/03/19/iêng
Eric Lippert

2
Tôi nghĩ rằng tôi đã nghĩ ra một cái gì đó. Nếu bạn có một Typebiến, bạn có thể sử dụng sự phản chiếu để tạo một thể hiện của kiểu đó. Và sau đó bạn có thể sử dụng một phương thức chung để trả về loại bạn muốn bằng cách suy ra nó từ một tham số của loại đó. Thật không may, bất kỳ phương thức phản chiếu nào tạo ra một thể hiện của một loại sẽ có kiểu trả về object, vì vậy CastByExamplephương thức chung của bạn cũng sẽ sử dụng object. Vì vậy, thực sự không có cách nào để làm điều này, và thậm chí nếu có, bạn sẽ làm gì với đối tượng mới được chọn? Bạn không thể sử dụng phương pháp của nó hoặc bất cứ điều gì vì bạn không biết loại của nó.
Kyle Delaney

@KyleDelaney Cảm ơn bạn, tôi hoàn toàn đồng ý! Như tôi đã cố gắng giải thích trong câu trả lời của mình, thật không hữu ích khi bỏ thứ gì đó sang một thứ khác mà không có lúc nào đó xác định Loại mà bạn đang thực sự sử dụng. Toàn bộ điểm của các loại là kiểm tra loại thời gian biên dịch. Nếu bạn chỉ cần thực hiện các cuộc gọi trên đối tượng, bạn có thể sử dụng objecthoặc dynamic. Nếu bạn muốn tải động các mô-đun bên ngoài, bạn có thể yêu cầu các lớp chia sẻ một giao diện chung và truyền đối tượng đến đó. Nếu bạn không kiểm soát mã của bên thứ ba, hãy tạo các trình bao bọc nhỏ và triển khai giao diện trên đó.
Zyphrax

Câu trả lời:


203

Dưới đây là một ví dụ về diễn viên và chuyển đổi:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Biên tập:

Một số người trong các ý kiến ​​nói rằng câu trả lời này không trả lời câu hỏi. Nhưng dòng (T) Convert.ChangeType(input, typeof(T))cung cấp giải pháp. Các Convert.ChangeTypephương pháp cố gắng để chuyển đổi bất kỳ đối tượng để Type cung cấp như là đối số thứ hai.

Ví dụ:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Tôi đã viết câu trả lời với Generics, bởi vì tôi nghĩ rằng đó là một rất có khả năng đăng ký mã mùi khi bạn muốn cast a somethingđể a something elsemà không xử lý một loại thực tế. Với các giao diện phù hợp không cần thiết 99,9% số lần. Có lẽ có một vài trường hợp cạnh khi phản ánh rằng nó có thể có ý nghĩa, nhưng tôi khuyên bạn nên tránh những trường hợp đó.

Chỉnh sửa 2:

Vài lời khuyên bổ sung:

  • Cố gắng giữ mã của bạn an toàn nhất có thể. Nếu trình biên dịch không biết loại, thì nó không thể kiểm tra xem mã của bạn có đúng không và những thứ như tự động hoàn thành sẽ không hoạt động. Nói một cách đơn giản: nếu bạn không thể dự đoán (các) loại tại thời gian biên dịch, thì trình biên dịch sẽ có thể như thế nào?
  • Nếu các lớp mà bạn đang làm việc thực hiện một giao diện chung , bạn có thể truyền giá trị cho giao diện đó. Mặt khác, hãy xem xét việc tạo giao diện của riêng bạn và yêu cầu các lớp thực hiện giao diện đó.
  • Nếu bạn đang làm việc với các thư viện bên ngoài mà bạn đang nhập động, thì hãy kiểm tra giao diện chung. Mặt khác, hãy xem xét việc tạo các lớp bao bọc nhỏ thực hiện giao diện.
  • Nếu bạn muốn thực hiện cuộc gọi trên đối tượng, nhưng không quan tâm đến loại, sau đó lưu giá trị trong một objecthoặc dynamicbiến.
  • Generics có thể là một cách tuyệt vời để tạo mã có thể tái sử dụng áp dụng cho rất nhiều loại khác nhau, mà không cần phải biết chính xác các loại liên quan.
  • Nếu bạn bị mắc kẹt thì hãy xem xét một cách tiếp cận hoặc mã tái cấu trúc khác. Mã của bạn có thực sự phải năng động không? Nó có phải chiếm bất kỳ loại nào không?

145
Tôi không biết làm thế nào điều này giúp OP. Cô ấy có một loại biến, không phải Tnhư vậy.
nawfal

12
@nawfal, về cơ bản là dòng Convert.ChangeType(input, typeof(T));đưa ra giải pháp. Bạn có thể dễ dàng thay thế typeof(T)bằng một loại biến hiện có. Một giải pháp tốt hơn (nếu có thể) sẽ là ngăn chặn tất cả các loại động.
Zyphrax

59
@Zyphrax, không, nó vẫn yêu cầu diễn viên Tkhông có sẵn.
nawfal

4
Tôi biết đối tượng kết quả thực sự là loại Tnhưng bạn vẫn chỉ nhận được objectmột tài liệu tham khảo. hmm, tôi thấy câu hỏi thú vị trong tiền đề rằng OP chỉ có Typebiến và không có thông tin nào khác. Như thể chữ ký của phương thức là Convert(object source, Type destination):) Tuy nhiên tôi nhận được điểm của bạn
nawfal

10
Làm thế nào đây là một giải pháp cho câu hỏi này? Tôi đã có cùng một vấn đề và tôi không có <T> chung chung. Tôi chỉ có một loại biến.
Nuri Tasdemir

114

Các câu trả lời khác không đề cập đến loại "động". Vì vậy, để thêm một câu trả lời, bạn có thể sử dụng loại "động" để lưu trữ đối tượng kết quả của mình mà không phải truyền đối tượng đã chuyển đổi bằng loại tĩnh.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Hãy nhớ rằng với việc sử dụng "động", trình biên dịch sẽ bỏ qua việc kiểm tra kiểu tĩnh có thể gây ra lỗi thời gian chạy nếu bạn không cẩn thận.


19
Đây là câu trả lời chính xác. Không có loại từ khóa động (changeObj) là "đối tượng". Với từ khóa động, nó hoạt động hoàn hảo và typeof (changeObject) phản ánh chính xác cùng loại với typeVar. Ngoài ra, bạn không cần (T) diễn viên mà bạn không thể làm nếu bạn không biết loại.
vội vàng

5
Tôi đã có ngoại lệ "Đối tượng phải triển khai IConvertible" trong khi sử dụng giải pháp này. Có ai giúp đỡ không?
Nuri Tasdemir

@NuriTasdemir Khó có thể nói, nhưng tôi tin rằng việc chuyển đổi bạn đang thực hiện là không thể nếu không có IConvertible. Các loại liên quan đến chuyển đổi của bạn là gì?
maulik13

Trong khi điều này hoạt động, có một hình phạt hiệu suất với việc sử dụng động lực. Tôi sẽ khuyên bạn không nên sử dụng chúng trừ khi bạn đang làm việc với các thời gian chạy khác (đó là động lực được thiết kế cho).
Bolo

19

Đây là phương pháp của tôi để truyền một đối tượng nhưng không phải là một biến loại chung, thay vì System.Typeđộng:

Tôi tạo một biểu thức lambda trong thời gian chạy bằng cách sử dụng System.Linq.Expressions, loại bỏ hộp Func<object, object>đầu vào của nó, thực hiện chuyển đổi loại mong muốn sau đó đưa ra kết quả được đóng hộp. Một loại mới không chỉ cần thiết cho tất cả các loại được truyền tới, mà còn cho các loại được truyền (vì bước unboxing). Việc tạo các biểu thức này rất tốn thời gian, vì sự phản chiếu, biên dịch và xây dựng phương thức động được thực hiện dưới mui xe. May mắn là một khi được tạo, các biểu thức có thể được gọi liên tục và không có chi phí cao, vì vậy tôi lưu trữ từng bộ đệm.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Lưu ý rằng đây không phải là phép thuật. Việc truyền không xảy ra trong mã, giống như với dynamictừ khóa, chỉ có dữ liệu cơ bản của đối tượng được chuyển đổi. Tại thời điểm biên dịch, chúng ta vẫn còn phải cố gắng tìm ra chính xác loại đối tượng của chúng ta có thể là gì, làm cho giải pháp này không thực tế. Tôi đã viết điều này như một hack để gọi các toán tử chuyển đổi được xác định bởi các loại tùy ý, nhưng có lẽ ai đó ngoài kia có thể tìm thấy một trường hợp sử dụng tốt hơn.


2
Yêu cầuusing System.Linq.Expressions;
Aaron D

4
Đối với tôi điều này bị vấn đề tương tự như câu trả lời của Zyphrax. Tôi không thể gọi các phương thức trên đối tượng được trả về bởi vì nó vẫn thuộc kiểu "đối tượng". Cho dù tôi sử dụng phương thức của anh ấy ("a" bên dưới) hay phương pháp của bạn ("b" bên dưới) tôi đều gặp lỗi tương tự trên (t) cast - "'t' là một biến nhưng nó được sử dụng như một loại.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla

@muusbolla Câu trả lời ban đầu của Zyphrax sử dụng các biến tổng quát và loại, không Type. Bạn không thể truyền bằng cú pháp truyền bình thường nếu tất cả những gì bạn có là đối tượng Loại. Nếu bạn muốn có thể sử dụng đối tượng như một số loại T tại thời gian biên dịch, không phải thời gian chạy, bạn cần truyền nó bằng một biến loại hoặc chỉ tên loại thực tế. Bạn có thể làm điều trước bằng cách sử dụng câu trả lời của Zaphrax.
Ashley

8

Đặt quyền anh và bỏ hộp sang một bên để đơn giản, không có hành động thời gian chạy cụ thể nào liên quan đến việc truyền theo hệ thống phân cấp thừa kế. Nó chủ yếu là một thứ thời gian biên dịch. Về cơ bản, một cast nói cho trình biên dịch coi giá trị của biến là một loại khác.

Bạn có thể làm gì sau khi diễn viên? Bạn không biết loại này, vì vậy bạn sẽ không thể gọi bất kỳ phương thức nào trên đó. Sẽ không có điều gì đặc biệt bạn có thể làm. Cụ thể, nó chỉ có thể hữu ích nếu bạn biết các loại có thể tại thời điểm biên dịch, truyền thủ công và xử lý từng trường hợp riêng biệt bằng các ifcâu lệnh:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...

1
Bạn có thể vui lòng giải thích rõ ràng hơn liên quan đến câu hỏi của tôi?
theringostarrs

Những gì tôi đang cố gắng giải thích là, những gì bạn sẽ có thể làm sau đó? Bạn không thể làm gì nhiều vì trình biên dịch C # yêu cầu nhập tĩnh để có thể thực hiện một việc hữu ích với đối tượng
Mehrdad Afshari

Bạn đúng. Tôi biết các loại dự kiến ​​của hai biến được gửi đến phương thức dưới dạng kiểu 'đối tượng'. Tôi muốn truyền tới các loại dự kiến ​​được lưu trữ trong các biến và thêm chúng vào bộ sưu tập. Dễ dàng hơn nhiều để phân nhánh trên loại và thử lỗi cast và bắt lỗi thông thường.
theringostarrs

4
Câu trả lời của bạn là tốt, nhưng chỉ cần kén chọn, tôi lưu ý rằng phôi không bao giờ ảnh hưởng đến các biến . Nó không bao giờ là quy phạm pháp luật để đúc một biến vào một biến kiểu khác; các loại biến là bất biến trong C #. Bạn chỉ có thể truyền giá trị được lưu trữ trong biến sang loại khác.
Eric Lippert

Việc giới thiệu kiểu gõ động của C # 4.0 có thay đổi câu trả lời này không?
Daniel T.

6

Làm thế nào bạn có thể làm điều đó? Bạn cần một biến hoặc trường loại T nơi bạn có thể lưu trữ đối tượng sau khi truyền, nhưng làm thế nào bạn có thể có một biến hoặc trường như vậy nếu bạn chỉ biết T khi chạy? Vì vậy, không, nó không thể.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?

3
Nếu bạn đang sử dụng một lớp chung, định nghĩa một phương thức có giá trị trả về của loại T, bạn có thể cần phải làm điều đó. Ví dụ: phân tích một chuỗi thành một thể hiện của T và trả về chuỗi đó.
Oliver Friedrich

7
Đây không phải là câu trả lời đúng. Xem câu trả lời của maulik13.
vội vàng

3
Bạn tìm thấy một CastTophương pháp ở Objectđâu trên thiên đường ?
ProfK

3

Khi nói đến việc đúc kiểu Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

Và bạn sẽ gọi nó như thế:

var enumValue = GetEnum(typeof(YourEnum), foo);

Điều này rất cần thiết đối với tôi trong trường hợp nhận giá trị thuộc tính Mô tả của một số loại enum theo giá trị int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

và sau đó:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

Ngoài ra (cách tiếp cận tốt hơn), việc đúc như vậy có thể trông như thế:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }

1

Sau khi không tìm thấy bất cứ điều gì xung quanh "Đối tượng phải triển khai ngoại lệ IConvertible" khi sử dụng câu trả lời của Zyphrax (ngoại trừ việc thực hiện giao diện) .. Tôi đã thử một cái gì đó hơi khác thường và hoạt động cho tình huống của mình.

Sử dụng gói nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);

1

Có hại, vấn đề là bạn không có chữ T.

bạn chỉ có một biến loại.

Gợi ý cho MS, nếu bạn có thể làm một cái gì đó như

TryCast<typeof(MyClass)>

nếu sẽ giải quyết tất cả các vấn đề của chúng tôi.


0

Tôi sẽ không bao giờ hiểu tại sao bạn cần tới 50 danh tiếng để để lại bình luận nhưng tôi chỉ cần nói rằng câu trả lời @Curt chính xác là những gì tôi đang tìm kiếm và hy vọng người khác.

Trong ví dụ của tôi, tôi có ActionFilterAttribution mà tôi đang sử dụng để cập nhật các giá trị của tài liệu vá json. Tôi đã không biết mô hình T là gì đối với tài liệu vá để tôi phải tuần tự hóa và giải tuần tự hóa nó thành một JsonPatchDocument đơn giản, sau đó sửa đổi nó, sau đó bởi vì tôi đã loại, tuần tự hóa và loại bỏ nó trở lại kiểu đó.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );

-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}

2
Bạn có thể vui lòng chỉ ra câu trả lời này khác với các câu trả lời khác như thế nào và giải pháp này phù hợp ở đâu?
Klaus Gütter

-2

thậm chí sạch hơn:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }

-2

Nếu bạn cần truyền các đối tượng trong thời gian chạy mà không biết loại đích, bạn có thể sử dụng sự phản chiếu để tạo một bộ chuyển đổi động.

Đây là phiên bản đơn giản hóa (không có phương thức tạo bộ đệm):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

sau đó bạn có thể gọi nó:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.