กำลังส่งตัวแปรโดยใช้ตัวแปร Type


281

ใน C # ฉันสามารถส่งตัวแปรชนิดของวัตถุไปยังตัวแปรประเภท T โดยที่ T ถูกกำหนดในตัวแปร Type ได้หรือไม่


12
ไม่เกี่ยวกับหัวข้ออย่างเคร่งครัด แต่คุณดูเหมือนจะคลุมเครือเกี่ยวกับสิ่งที่ "นักแสดง" หมายความว่าอาจเป็นความคิดที่ดีที่จะเข้าใจอย่างแม่นยำว่าจุดประสงค์และความหมายของผู้ดำเนินการนักแสดงคืออะไร นี่คือการเริ่มต้นที่ดี: blogs.msdn.com/ericlippert/archive/2009/03/19/...
เอริค Lippert

2
ฉันคิดว่าฉันคิดอะไรบางอย่าง หากคุณมีTypeตัวแปรคุณสามารถใช้การสะท้อนเพื่อสร้างอินสแตนซ์ของประเภทนั้น จากนั้นคุณสามารถใช้วิธีการทั่วไปเพื่อคืนค่าชนิดที่คุณต้องการโดยอนุมานจากพารามิเตอร์ประเภทนั้น น่าเสียดายที่วิธีการสะท้อนใด ๆ ที่สร้างอินสแตนซ์ของประเภทจะมีประเภทผลตอบแทนobjectดังนั้นCastByExampleวิธีการทั่วไปของคุณก็จะใช้objectเช่นกัน ดังนั้นจึงไม่มีวิธีการทำเช่นนี้และแม้ว่าจะมีสิ่งที่คุณจะทำอย่างไรกับวัตถุหล่อใหม่? คุณไม่สามารถใช้วิธีการหรืออะไรก็ได้เพราะคุณไม่รู้ประเภทของมัน
Kyle Delaney

@ KyleDelaney ขอบคุณฉันเห็นด้วยอย่างสมบูรณ์! ขณะที่ฉันพยายามอธิบายในคำตอบของฉันมันไม่มีประโยชน์ที่จะนำบางสิ่งไปสู่สิ่งที่แตกต่างโดยไม่มีการกำหนดประเภทที่คุณใช้งานจริง จุดรวมของประเภทคือการตรวจสอบประเภทเวลาคอมไพเลอร์ หากคุณเพียงแค่ต้องทำสายบนวัตถุที่คุณสามารถใช้หรือobject dynamicหากคุณต้องการโหลดโมดูลภายนอกแบบไดนามิกคุณสามารถให้คลาสแบ่งใช้อินเทอร์เฟซทั่วไปและใช้วัตถุนั้น หากคุณไม่ได้ควบคุมรหัสบุคคลที่สามให้สร้าง wrapper ขนาดเล็กและใช้งานส่วนต่อประสานนั้น
Zyphrax

คำตอบ:


203

นี่คือตัวอย่างของนักแสดงและผู้เปลี่ยนใจเลื่อมใส:

using System;

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

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

แก้ไข:

บางคนในความเห็นบอกว่าคำตอบนี้ไม่ได้ตอบคำถาม แต่สายการ(T) Convert.ChangeType(input, typeof(T))ให้บริการแก้ปัญหา Convert.ChangeTypeวิธีการพยายามที่จะแปลงวัตถุใด ๆ ที่จะพิมพ์ให้เป็นอาร์กิวเมนต์ที่สอง

ตัวอย่างเช่น:

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);

ผมเคยเขียนคำตอบกับ generics เพราะผมคิดว่ามันเป็นสัญญาณที่มีโอกาสมากในการดมกลิ่นรหัสเมื่อคุณต้องการที่จะโยนa somethingไปa something elseโดยไม่ต้องจัดการชนิดที่เกิดขึ้นจริง ด้วยอินเทอร์เฟซที่เหมาะสมที่ไม่ควรจำเป็น 99.9% ของเวลา อาจมีบางกรณีขอบเมื่อมันมาถึงสะท้อนว่ามันอาจทำให้รู้สึก แต่ฉันอยากจะแนะนำให้หลีกเลี่ยงกรณีเหล่านั้น

แก้ไข 2:

เคล็ดลับพิเศษ:

  • พยายามที่จะรักษารหัสของคุณเป็นประเภทที่ปลอดภัยที่สุด หากคอมไพเลอร์ไม่ทราบประเภทนั้นจะไม่สามารถตรวจสอบว่ารหัสของคุณถูกต้องหรือไม่และการเติมข้อความอัตโนมัติจะไม่ทำงาน พูดง่ายๆว่า: หากคุณไม่สามารถคาดเดาประเภทของเวลาในการคอมไพล์แล้วคอมไพเลอร์จะทำอย่างไร
  • หากคลาสที่คุณทำงานด้วยใช้อินเตอร์เฟสทั่วไปคุณสามารถส่งค่าไปยังอินเตอร์เฟสนั้นได้ มิฉะนั้นให้พิจารณาสร้างอินเตอร์เฟสของคุณเองและให้คลาสใช้อินเทอร์เฟซนั้น
  • หากคุณทำงานกับไลบรารีภายนอกที่คุณกำลังอิมพอร์ตแบบไดนามิกให้ตรวจสอบอินเตอร์เฟสทั่วไป มิฉะนั้นให้พิจารณาสร้างคลาส wrapper ขนาดเล็กที่ใช้อินเตอร์เฟส
  • หากคุณต้องการโทรหาวัตถุ แต่ไม่สนใจประเภทนั้นให้เก็บค่าไว้ในตัวแปรobjectหรือdynamic
  • Genericsอาจเป็นวิธีที่ยอดเยี่ยมในการสร้างรหัสที่สามารถนำกลับมาใช้ใหม่ได้ซึ่งมีหลากหลายประเภท
  • หากคุณติดค้างอยู่ให้พิจารณาวิธีการที่แตกต่างกันหรือ refactor รหัส รหัสของคุณจำเป็นต้องเป็นแบบไดนามิกหรือไม่ มันต้องมีบัญชีสำหรับประเภทใดมี?

145
ฉันไม่รู้ว่าสิ่งนี้ช่วย OP ได้อย่างไร เธอมีตัวแปรประเภทไม่Tเช่นนั้น
nawfal

12
@nawfal โดยทั่วไปบรรทัดConvert.ChangeType(input, typeof(T));ให้วิธีแก้ปัญหา คุณสามารถแทนที่typeof(T)ด้วยตัวแปรชนิดที่มีอยู่ได้อย่างง่ายดาย ทางออกที่ดีกว่า (ถ้าเป็นไปได้) คือการป้องกันไม่ให้พิมพ์แบบไดนามิกเข้าด้วยกัน
Zyphrax

59
@Zyphrax ไม่จำเป็นต้องมี cast Tซึ่งไม่สามารถใช้งานได้
nawfal

4
ฉันรู้ว่าวัตถุผลลัพธ์เป็นจริงของประเภทTแต่คุณก็ยังคงได้รับการobjectอ้างอิง อืมฉันพบคำถามที่น่าสนใจในสมมติฐานที่ว่า OP มีเพียงTypeตัวแปรและไม่มีข้อมูลอื่น ราวกับว่าลายเซ็นวิธีการคือConvert(object source, Type destination):) อย่างไรก็ตามฉันได้รับจุด ur
nawfal

10
นี่เป็นวิธีแก้ปัญหาสำหรับคำถามนี้อย่างไร ฉันมีปัญหาเดียวกันและฉันไม่มี <T> ทั่วไป ฉันมีตัวแปรประเภทเท่านั้น
นูริ Tasdemir

114

คำตอบอื่น ๆ ไม่ได้กล่าวถึงประเภท "ไดนามิก" ดังนั้นเพื่อเพิ่มคำตอบอีกหนึ่งข้อคุณสามารถใช้ชนิด "ไดนามิก" เพื่อเก็บวัตถุที่เป็นผลลัพธ์ของคุณโดยไม่ต้องแปลงวัตถุที่มีชนิดคงที่

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

โปรดทราบว่าด้วยการใช้ "ไดนามิก" คอมไพเลอร์จะข้ามการตรวจสอบประเภทคงที่ซึ่งอาจแนะนำข้อผิดพลาดรันไทม์ที่เป็นไปได้ถ้าคุณไม่ระวัง


19
นี่คือคำตอบที่ถูกต้อง หากไม่มีคีย์เวิร์ดประเภทไดนามิก (changeObj) คือ "object" ด้วยคีย์เวิร์ดแบบไดนามิกมันทำงานได้อย่างไร้ที่ติและ typeof (เปลี่ยนแปลงอ็อบเจ็ก) จะแสดงประเภทเดียวกันกับ typeVar อย่างถูกต้อง นอกจากนี้คุณไม่จำเป็นต้องโยน (T) ซึ่งคุณไม่สามารถทำได้ถ้าคุณไม่รู้ประเภท
Rushinge

5
ฉันได้รับข้อยกเว้น "Object ต้องใช้ IConvertible" ในขณะที่ใช้โซลูชันนี้ ความช่วยเหลือใด ๆ
นูริ Tasdemir

@NuriTasdemir ยากที่จะบอก แต่ฉันเชื่อว่าการแปลงที่คุณทำไม่สามารถทำได้หากไม่มี IConvertible ประเภทของการแปลงของคุณเกี่ยวข้องกับอะไร?
maulik13

ในขณะที่ใช้งานได้มีโทษประสิทธิภาพกับการใช้การเปลี่ยนแปลง ฉันจะแนะนำให้ต่อต้านการใช้พวกเขาจนกว่าคุณจะทำงานร่วมกับ runtimes อื่น ๆ (ซึ่งเป็นสิ่งที่เปลี่ยนแปลงได้รับการออกแบบสำหรับ)
Bolo

19

นี่คือวิธีการของฉันในการโยนวัตถุ แต่ไม่ใช่ตัวแปรชนิดทั่วไปแทนที่จะเป็นSystem.Typeแบบไดนามิก:

ฉันสร้างนิพจน์แลมบ์ดาในขณะใช้งานโดยใช้System.Linq.ExpressionsชนิดFunc<object, object>ที่ unbox อินพุตของมันทำการแปลงชนิดที่ต้องการจากนั้นให้กล่องผลลัพธ์ จำเป็นต้องสร้างใหม่ไม่เพียง แต่สำหรับทุกประเภทที่ได้รับการส่งไปยัง แต่สำหรับประเภทที่ได้รับการส่ง (เนื่องจากขั้นตอนการแกะกล่อง) การสร้างนิพจน์เหล่านี้ใช้เวลานานเนื่องจากการสะท้อนการรวบรวมและการสร้างวิธีแบบไดนามิกที่ทำภายใต้ประทุน โชคดีที่ได้สร้างขึ้นครั้งหนึ่งนิพจน์สามารถเรียกใช้ซ้ำได้และไม่มีค่าใช้จ่ายสูงดังนั้นฉันจึงแคชแต่ละรายการ

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);
}

โปรดทราบว่านี่ไม่ใช่เวทมนต์ การร่ายไม่ได้เกิดขึ้นในโค้ดเช่นเดียวกับที่ใช้กับdynamicคำสำคัญเฉพาะข้อมูลพื้นฐานของวัตถุเท่านั้นที่จะถูกแปลง ในการรวบรวมเวลาเรายังคงต้องใช้ความระมัดระวังอย่างถี่ถ้วนว่าสิ่งที่ประเภทของวัตถุของเราอาจเป็นทำให้การแก้ปัญหานี้ทำไม่ได้ ฉันเขียนสิ่งนี้เป็นแฮ็คเพื่อเรียกใช้ตัวดำเนินการแปลงที่กำหนดโดยประเภทตามอำเภอใจ แต่อาจมีบางคนที่อยู่ข้างนอกสามารถพบกรณีการใช้ที่ดีกว่า


2
ต้องการusing System.Linq.Expressions;
Aaron D

4
สำหรับฉันแล้วนี่เป็นปัญหาเดียวกับคำตอบของ Zyphrax ฉันไม่สามารถเรียกใช้เมธอดบนวัตถุที่ส่งคืนได้เนื่องจากมันยังคงเป็นประเภท "วัตถุ" ไม่ว่าฉันจะใช้วิธีการของเขา ("a" ด้านล่าง) หรือวิธีการของคุณ ("b" ด้านล่าง) ฉันได้รับข้อผิดพลาดเดียวกันกับ cast (t) - "'t' เป็นตัวแปร แต่มันถูกใช้เหมือนประเภทType t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla

คำตอบเดิม @muusbolla Zyphrax Typeของใช้ยาชื่อสามัญและตัวแปรชนิดไม่ คุณไม่สามารถส่งโดยใช้ไวยากรณ์การส่งแบบปกติถ้าสิ่งที่คุณมีคือวัตถุประเภท หากคุณต้องการที่จะใช้วัตถุเป็น T ประเภทในเวลารวบรวมไม่ runtime คุณต้องโยนมันโดยใช้ตัวแปรประเภทหรือเพียงแค่ชื่อประเภทจริง คุณสามารถทำได้โดยใช้คำตอบของ Zaphrax
แอชลีย์

8

วางมวยและ unboxing กันเพื่อความง่ายไม่มีการดำเนินการเฉพาะรันไทม์ที่เกี่ยวข้องในการคัดเลือกตามลำดับชั้นการสืบทอด มันเป็นเรื่องของการรวบรวมเวลา โดยพื้นฐานแล้วนักแสดงบอกคอมไพเลอร์ในการรักษาค่าของตัวแปรเป็นประเภทอื่น

คุณสามารถทำอะไรหลังจากโยน? คุณไม่รู้ประเภทดังนั้นคุณจะไม่สามารถเรียกวิธีการใด ๆ ไม่มีสิ่งใดเป็นพิเศษที่คุณสามารถทำได้ โดยเฉพาะมันจะมีประโยชน์ก็ต่อเมื่อคุณรู้ประเภทที่เป็นไปได้ในเวลารวบรวมรวบรวมหล่อด้วยตนเองและจัดการแต่ละกรณีแยกต่างหากด้วยifคำสั่ง:

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

1
คุณช่วยอธิบายให้ชัดเจนขึ้นเกี่ยวกับคำถามของฉันได้ไหม
theringostarrs

สิ่งที่ฉันพยายามอธิบายก็คือคุณจะทำอะไรได้หลังจากนั้น คุณไม่สามารถทำอะไรได้มากนักเนื่องจากคอมไพเลอร์ C # ต้องการการพิมพ์แบบสแตติกเพื่อให้สามารถทำสิ่งที่มีประโยชน์กับวัตถุได้
Mehrdad Afshari

คุณถูก. ฉันรู้ชนิดที่คาดหวังของตัวแปรสองตัวซึ่งถูกส่งไปยังวิธีการเป็นประเภท 'วัตถุ' ฉันต้องการแปลงเป็นประเภทที่คาดไว้ซึ่งเก็บไว้ในตัวแปรและเพิ่มลงในคอลเล็กชัน แตกแขนงได้ง่ายกว่ามากและพยายามโยนแบบปกติและตรวจจับข้อผิดพลาด
theringostarrs

4
คำตอบของคุณเป็นสิ่งที่ดี แต่ก็จะจู้จี้จุกจิกผมทราบว่าบรรยากาศไม่ส่งผลกระทบต่อตัวแปร มันไม่ถูกกฎหมายในการแปลงตัวแปรให้เป็นตัวแปรชนิดอื่น ประเภทตัวแปรไม่แปรเปลี่ยนใน C # คุณสามารถส่งค่าที่เก็บไว้ในตัวแปรไปเป็นประเภทอื่นเท่านั้น
Eric Lippert

การแนะนำการพิมพ์แบบไดนามิกของ C # 4.0 เปลี่ยนคำตอบนี้หรือไม่?
Daniel T.

6

คุณจะทำเช่นนั้นได้อย่างไร คุณต้องการตัวแปรหรือฟิลด์ชนิด T ที่คุณสามารถเก็บวัตถุหลังการร่าย แต่คุณจะมีตัวแปรหรือฟิลด์เช่นนี้ได้อย่างไรถ้าคุณรู้จัก T เฉพาะตอนรันไทม์ ดังนั้นไม่เป็นไปไม่ได้

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
หากคุณกำลังใช้คลาสทั่วไปนั่นจะกำหนดวิธีที่มีค่าส่งคืนประเภท T คุณอาจต้องดำเนินการดังกล่าว เช่นการแยกสตริงเป็นอินสแตนซ์ของ T แล้วส่งคืนนั้น
Oliver Friedrich

7
นี่ไม่ใช่คำตอบที่ถูกต้องโชคดี ดูคำตอบของ maulik13
Rushinge

3
คุณพบCastToวิธีในชื่อของสวรรค์ที่ไหนObject?
ProfK

3

เมื่อพูดถึงการคัดเลือก Enum type:

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

        return null;
    }

และคุณจะเรียกมันว่า:

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

นี่เป็นสิ่งสำคัญสำหรับฉันในกรณีที่รับค่าแอ็ตทริบิวต์คำอธิบายของ enum หลายชนิดตามค่า 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();
    }

แล้ว:

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

อีกวิธีหนึ่ง (วิธีที่ดีกว่า) การคัดเลือกนักแสดงเช่นนี้อาจเป็นเช่นนั้น:

 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

หลังจากไม่พบสิ่งใดเพื่อหลีกเลี่ยงข้อยกเว้น "Object ต้องใช้ IConvertible" เมื่อใช้คำตอบของ Zyphrax (ยกเว้นสำหรับการนำอินเตอร์เฟสไปใช้) ฉันพยายามทำสิ่งที่ผิดปกติเล็กน้อยและทำงานกับสถานการณ์ของฉัน

ใช้แพ็คเกจ nuget Newtonsoft.Json ...

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

1

อันตรายปัญหาคือคุณไม่มี T

คุณมีตัวแปร Type เท่านั้น

แนะนำให้ MS ถ้าคุณสามารถทำอะไรเช่น

TryCast<typeof(MyClass)>

ถ้าจะแก้ปัญหาทั้งหมดของเรา


0

ฉันจะไม่เข้าใจว่าทำไมคุณถึงต้องการ 50 ชื่อเสียงในการแสดงความคิดเห็น แต่ฉันต้องบอกว่า @Curt คำตอบคือสิ่งที่ฉันกำลังมองหาและหวังว่าจะมีคนอื่น

ในตัวอย่างของฉันฉันมี ActionFilterAttribute ที่ฉันใช้เพื่ออัพเดตค่าของเอกสารแพตช์ json ฉันไม่ได้ทำในสิ่งที่รุ่น T สำหรับเอกสารแก้ไขให้ฉันต้องทำให้เป็นอันดับ & deserialize ให้เป็น JsonPatchDocument ธรรมดาแก้ไขมันเพราะฉันมีประเภททำให้เป็นอนุกรม

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
คุณช่วยชี้ให้เห็นว่าคำตอบนี้แตกต่างจากคำตอบอื่น ๆ ได้อย่างไรและคำตอบนี้เหมาะสมอย่างไร
Klaus Gütter


-2

หากคุณต้องการส่งวัตถุไปที่รันไทม์โดยไม่ทราบประเภทปลายทางคุณสามารถใช้การสะท้อนเพื่อสร้างตัวแปลงแบบไดนามิก

นี่เป็นเวอร์ชั่นที่เรียบง่าย (ไม่มีวิธีการสร้างแคช):

    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 });
            }
    }

จากนั้นคุณสามารถเรียกมันว่า:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.