สร้างอินสแตนซ์ประเภททั่วไปที่ตัวสร้างต้องการพารามิเตอร์หรือไม่


230

หากBaseFruitมีคอนสตรัคเตอร์ที่ยอมรับได้int weightฉันจะยกตัวอย่างผลไม้ในวิธีทั่วไปแบบนี้ได้ไหม

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

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

- อัปเดต -
ดังนั้นจึงไม่สามารถแก้ไขได้ด้วยข้อ จำกัด แต่อย่างใด จากคำตอบมีสามวิธีแก้ปัญหาผู้สมัคร:

  • รูปแบบโรงงาน
  • การสะท้อน
  • Activator

ฉันมักจะคิดว่าการไตร่ตรองนั้นสะอาดน้อยที่สุด แต่ฉันก็ไม่สามารถตัดสินใจได้ระหว่างสองสิ่งนี้


1
BTW: วันนี้ฉันอาจจะแก้ปัญหานี้ด้วยห้องสมุด IoC ที่เลือก
Boris Callens

Reflection และ Activator นั้นเกี่ยวข้องกันอย่างใกล้ชิด
Rob Vermeulen

คำตอบ:


335

นอกจากนี้ตัวอย่างที่ง่ายกว่า:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

โปรดทราบว่าการใช้ข้อ จำกัด ใหม่ () บน T เป็นเพียงการตรวจสอบคอมไพเลอร์สำหรับตัวสร้างพารามิเตอร์แบบไม่มีพารามิเตอร์ ณ เวลารวบรวมรหัสจริงที่ใช้ในการสร้างประเภทคือคลาส Activator

คุณจะต้องให้แน่ใจว่าตัวคุณเองเกี่ยวกับคอนสตรัคเตอร์เฉพาะที่มีอยู่และความต้องการประเภทนี้อาจเป็นกลิ่นรหัส (หรือเป็นสิ่งที่คุณควรพยายามหลีกเลี่ยงในเวอร์ชันปัจจุบันใน c #)


เนื่องจากตัวสร้างนี้อยู่บน baseclass (BaseFruit) ฉันรู้ว่ามันจะมีตัวสร้าง แต่ถ้าวันหนึ่งฉันตัดสินใจว่าเบสฟรุ๊ตต้องการพารามิเตอร์มากกว่านี้ฉันก็จะเมา จะมองเข้าไปในชั้นเรียน ACtivator ไม่เคยได้ยินมาก่อน
Boris Callens

3
อันนี้ใช้ได้ดี นอกจากนี้ยังมี CreateInstance <T> () ขั้นตอน แต่ที่ไม่ได้มีการโอเวอร์โหลดสำหรับพารามิเตอร์สำหรับ Rason บาง ..
บอริส Callens

20
new object[] { weight }ไม่มีความจำเป็นที่จะใช้เป็น CreateInstanceมีการประกาศกับ params, เพื่อให้คุณสามารถเพียงแค่ทำpublic static object CreateInstance(Type type, params object[] args) return (T) Activator.CreateInstance(typeof(T), weight);หากมีหลายพารามิเตอร์ให้ส่งพารามิเตอร์เหล่านี้เป็นอาร์กิวเมนต์แยกต่างหาก เฉพาะในกรณีที่คุณมีอยู่แล้วนับสร้างของพารามิเตอร์ที่คุณควรจะรำคาญที่จะแปลงเป็นและผ่านที่object[] CreateInstance
ErikE

2
นี่จะมีปัญหาเรื่องประสิทธิภาพที่ฉันได้อ่าน ใช้แลมบ์ดาที่คอมไพล์แทน vagifabilov.wordpress.com/2010/04/02/…
เดวิด

1
@RobVermeulen - ฉันคิดว่าบางสิ่งบางอย่างเช่นคุณสมบัติคงที่ในแต่ละชั้นผลไม้ที่มีการFuncสร้างตัวอย่างใหม่ สมมติว่าการใช้งานเป็นตัวสร้างApple new Apple(wgt)จากนั้นเพิ่มAppleระดับคำนิยามนี้: static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);ในโรงงานกำหนดpublic static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } การใช้งาน: Factory.CreateFruit(57.3f, Apple.CreateOne);- ที่สร้างและส่งกลับด้วยApple weight=57.3f
ToolmakerSteve

92

คุณไม่สามารถใช้ตัวสร้างพารามิเตอร์ คุณสามารถใช้ตัวสร้างแบบไม่มีพารามิเตอร์ได้หากคุณมีwhere T : new()ข้อ จำกัด ""

มันเจ็บปวด แต่ชีวิตก็คือ :(

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


2
อย่างน้อยคุณก็สามารถทำข้อ จำกัด เช่นนี้ได้ - Java ทำให้ฉันผิดหวังอยู่เสมอ
Marcel Jackwerth

@ JonSkeet: ถ้าฉันเปิดเผย API กับ. NET ทั่วไปที่จะเรียกใน VB6.0 .. มันยังใช้งานได้หรือไม่
Roy Lee

@ Roylee: ฉันไม่รู้ แต่ฉันไม่สงสัยเลย
Jon Skeet

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

ข้อ จำกัด คอนสตรัคเตอร์พารามิเตอร์สามารถจัดการได้ในลักษณะเดียวกัน (โดยใช้วิธีการจากโรงงานและพารามิเตอร์ทั่วไปสำหรับชนิดส่งคืน) ไม่ว่าในกรณีใดจะป้องกันโค้ดที่เขียนในภาษาที่ไม่รองรับคุณสมบัติดังกล่าวจากการอ้างว่าใช้อินเทอร์เฟซโดยไม่ต้องกำหนดประเภทสแตติกที่เหมาะสมดังนั้นโค้ดที่เขียนโดยใช้ภาษาดังกล่าวอาจล้มเหลวในขณะรันไทม์ รหัส.
supercat

61

ใช่; เปลี่ยนสถานที่ของคุณที่จะ:

where T:BaseFruit, new()

อย่างไรก็ตามสิ่งนี้ใช้ได้กับตัวสร้างแบบไร้พารามิเตอร์เท่านั้น คุณจะต้องมีวิธีอื่นในการตั้งค่าคุณสมบัติของคุณ (การตั้งค่าคุณสมบัติเองหรือสิ่งที่คล้ายกัน)


หากคอนสตรัคเตอร์ไม่มีพารามิเตอร์สิ่งนี้ดูจะปลอดภัยสำหรับฉัน
PerpetualStudent

คุณช่วยชีวิตฉันไว้ ฉันไม่สามารถจัดการเพื่อ จำกัด T เป็นคลาสและคำหลักใหม่ ()
Genotypek

28

ทางออกที่ง่ายที่สุด Activator.CreateInstance<T>()


1
ขอบคุณสำหรับคำแนะนำมันทำให้ฉันต้องอยู่ตรงไหน แม้ว่าสิ่งนี้จะไม่อนุญาตให้คุณใช้ตัวสร้างแบบกำหนดพารามิเตอร์ แต่คุณสามารถใช้ตัวแปรที่ไม่ใช่แบบทั่วไป: Activator.CreateInstance (typeof (T), ออบเจ็กต์ใหม่ [] {... }) โดยที่ออบเจ็กต์อาร์เรย์มีอาร์กิวเมนต์สำหรับคอนสตรัคเตอร์
Rob Vermeulen

19

ดังที่จอนชี้ให้เห็นว่านี่คือชีวิตสำหรับการบังคับคอนสตรัคเตอร์ที่ไม่มีพารามิเตอร์ อย่างไรก็ตามโซลูชันที่แตกต่างคือการใช้รูปแบบจากโรงงาน นี่คือข้อ จำกัด ได้อย่างง่ายดาย

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

อีกตัวเลือกหนึ่งคือการใช้วิธีการทำงาน ผ่านวิธีการของโรงงาน

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

2
ข้อเสนอแนะที่ดี - แต่ถ้าคุณไม่ระวังคุณสามารถจบลงในนรกของ Java DOM API ที่มีโรงงานมากมาย :(
จอนสกีต

ใช่นี่เป็นวิธีการแก้ปัญหาที่ฉันคิดเอง แต่ฉันก็หวังว่าจะมีอะไรบางอย่างอยู่ในข้อ จำกัด เดาไม่ได้แล้ว ..
บอริส Callens

@boris น่าเสียดายภาษา จำกัด คุณกำลังมองหาไม่ได้อยู่ที่จุดนี้ในเวลา
JaredPar

11

คุณสามารถทำได้โดยใช้การสะท้อน:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

แก้ไข:เพิ่มคอนสตรัค == ตรวจสอบ null

แก้ไข:ตัวแปรที่เร็วกว่าโดยใช้แคช:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

แม้ว่าฉันจะไม่ชอบค่าใช้จ่ายในการสะท้อน แต่อย่างที่คนอื่น ๆ ได้อธิบายนี่เป็นเพียงวิธีที่เป็นอยู่ในปัจจุบัน เมื่อเห็นว่าคอนสตรัคนี้จะไม่ถูกเรียกมากเกินไปฉันสามารถทำสิ่งนี้ได้ หรือโรงงาน ยังไม่รู้
Boris Callens

นี่เป็นวิธีที่ฉันชอบในขณะนี้เพราะมันไม่ได้เพิ่มความซับซ้อนในด้านการเรียกร้อง
Rob Vermeulen

แต่ตอนนี้ฉันได้อ่านเกี่ยวกับคำแนะนำของ Activator ซึ่งมีความขุ่นเคืองคล้ายกับวิธีแก้ปัญหาการสะท้อนกลับข้างต้น แต่ด้วยรหัสบรรทัดที่น้อยลง :) ฉันจะไปที่ตัวเลือก Activator
Rob Vermeulen

1

นอกเหนือจากข้อเสนอแนะของ user1471935:

ในการสร้างอินสแตนซ์ของคลาสทั่วไปโดยใช้ Constructor ที่มีพารามิเตอร์อย่างน้อยหนึ่งพารามิเตอร์คุณสามารถใช้คลาส Activator ได้แล้ว

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

รายการของวัตถุคือพารามิเตอร์ที่คุณต้องการจัดหา ตามที่ Microsoft :

CreateInstance [... ] สร้างตัวอย่างของประเภทที่ระบุโดยใช้ตัวสร้างที่ตรงกับพารามิเตอร์ที่ระบุมากที่สุด

นอกจากนี้ยังมีรุ่นทั่วไปของ CreateInstance ( CreateInstance<T>()) แต่ก็ไม่อนุญาตให้คุณจัดหาพารามิเตอร์คอนสตรัคเตอร์


1

ฉันสร้างวิธีนี้:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

ฉันใช้วิธีนี้:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

รหัส:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

0

เมื่อเร็ว ๆ นี้ฉันเจอปัญหาที่คล้ายกันมาก แค่ต้องการแบ่งปันโซลูชันของเรากับคุณทุกคน ฉันต้องการให้ฉันสร้างอินสแตนซ์ของCar<CarA>วัตถุจาก json ที่ใช้ซึ่งมี enum:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

-2

มันยังคงเป็นไปได้ด้วยประสิทธิภาพสูงโดยทำสิ่งต่อไปนี้:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

และ

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

คลาสที่เกี่ยวข้องต้องได้รับมาจากอินเทอร์เฟซนี้และเริ่มต้นตามลำดับ โปรดทราบว่าในกรณีของฉันรหัสนี้เป็นส่วนหนึ่งของคลาสโดยรอบซึ่งมี <T> เป็นพารามิเตอร์ทั่วไปอยู่แล้ว R ในกรณีของฉันยังเป็นคลาสอ่านอย่างเดียว IMO ความพร้อมใช้งานสาธารณะของฟังก์ชัน Initialize () ไม่มีผลเสียต่อการเปลี่ยนแปลงไม่ได้ ผู้ใช้ของคลาสนี้สามารถวางวัตถุอื่นใน แต่จะไม่แก้ไขคอลเลกชันพื้นฐาน

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