มีวิธีแก้ไขชั่วคราวในการใช้วิธีคงที่โดยคลาสทั่วไปหรือไม่?

ฉันมีปัญหาค่อนข้างง่าย แต่ดูเหมือนว่าจะไม่มีวิธีแก้ปัญหาภายใน C#

ฉันมีคลาส Foo ประมาณ 100 คลาสโดยแต่ละคลาสใช้เมธอด static FromBytes() นอกจากนี้ยังมีคลาสทั่วไปบางคลาสที่จะใช้วิธีการเหล่านี้เพื่อ FromBytes() ของตัวเอง แต่คลาสทั่วไปไม่สามารถใช้เมธอด static FromBytes() ได้ เนื่องจาก T.FromBytes(...) ผิดกฎหมาย

ฉันพลาดบางสิ่งบางอย่างหรือไม่มีทางใช้ฟังก์ชันนี้ได้หรือไม่?

public class Foo1
{
    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // build Foo1 instance
        return new Foo1()
        {
            Property1 = bytes[index++],
            Property2 = bytes[index++],
            // [...]
            Property10 = bytes[index++]
        };
    }

    public int Property1 { get; set; }
    public int Property2 { get; set; }
    // [...]
    public int Property10 { get; set; }
}

//public class Foo2 { ... }
// [...]
//public class Foo100 { ... }

// Generic class which needs the static method of T to work
public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(T.FromBytes(bytes, ref index)); // T.FromBytes(...) is illegal
        }

        return listOfFoo;
    }
}

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


person Karsten Gutjahr    schedule 11.11.2013    source แหล่งที่มา
comment
ลองใช้วิธีขยาย   -  person Hossain Muctadir    schedule 11.11.2013
comment
สวัสดี @Muctadir น่าเสียดายที่ฉันมีปัญหาเดียวกันในวิธีการขยายแบบทั่วไปเช่นเดียวกับในคลาสทั่วไป   -  person Karsten Gutjahr    schedule 11.11.2013
comment
คุณจะเรียกเมธอด ListOfFoo<T>.FromBytes(...) แบบคงที่ได้อย่างไร คุณจะต้องระบุอาร์กิวเมนต์ประเภทอยู่แล้วใช่ไหม ตัวอย่างเช่น: ListOfFoo<Foo1>.FromBytes(...) จากนั้น คุณสามารถส่งผู้รับมอบสิทธิ์เป็นพารามิเตอร์เพิ่มเติมได้ (เช่น บริการที่เสนอ) หรือคุณเรียกมันผ่านการไตร่ตรอง?   -  person gehho    schedule 11.11.2013


คำตอบ (8)


ตัวเลือกที่ดีที่สุดคือเพียงยอมรับฟังก์ชัน FromBytes เฉพาะในฐานะผู้รับมอบสิทธิ์ให้กับฟังก์ชัน FromBytes ทั่วไปของคุณ วิธีนี้จะหลีกเลี่ยงทั้งต้นทุนด้านประสิทธิภาพและการขาดการตรวจสอบเวลาคอมไพล์ที่มาพร้อมกับการใช้การสะท้อนกลับ

public delegate T FromBytesFunc<T>(byte[] bytes, ref int index);
public static List<T> FromBytes<T>(byte[] bytes, ref int index,
    FromBytesFunc<T> function)
{
    var count = bytes[index++];
    var listOfFoo = new List<T>();
    for (var i = 0; i < count; i++)
    {
        listOfFoo.Add(function(bytes, ref index));
    }

    return listOfFoo;
}

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

var list = SomeClass.FromBytes(bytes, ref index, Foo1.FromBytes);
person Servy    schedule 11.11.2013

ปัญหาคือคุณกำลังพยายามใช้ FromBytes เป็นวิธีการขยายเมื่อไม่ใช่ จากสิ่งที่ฉันรวบรวม คุณกำลังพยายามเรียก FromBytes ที่เหมาะสมสำหรับสิ่งใดก็ตามที่ <T> เป็น ตามที่กำหนดไว้ในคลาสใดก็ตามที่คุณได้สร้างไว้สำหรับ T

สิ่งที่คุณต้องทำคือเรียกวิธีการผ่านการไตร่ตรอง

ลองดูหัวข้อเหล่านี้บางส่วนเพื่อขอความช่วยเหลือในการบรรลุเป้าหมายนี้

ใช้ Reflection เพื่อเรียกวิธีการทั่วไปบนวัตถุ อินสแตนซ์ที่มีลายเซ็น: SomeObject.SomeGenericInstanceMethod‹T›(T อาร์กิวเมนต์)

ฉันจะใช้การสะท้อนกลับเพื่อเรียกวิธีการทั่วไปได้อย่างไร

การเรียกวิธีการทั่วไปโดยใช้การสะท้อนกลับใน .NET

จะเรียกวิธีการทั่วไปด้วยวัตถุ Type ที่กำหนดได้อย่างไร< /ก>

person Smeegs    schedule 11.11.2013
comment
หากฉันใช้การสะท้อนกลับเพื่อค้นหาประเภทฉันสามารถเรียก `Foo42.FromBytes()´ ได้ แต่ฉันไม่สามารถเพิ่มอินสแตนซ์ผลลัพธ์ลงในรายการได้: 'listOf.Add(Foo42.FromBytes(Bytes, ref index));' ให้ประเภทอาร์กิวเมนต์ 'Foo42' แก่ฉันไม่สามารถกำหนดให้กับประเภทพารามิเตอร์ T.. - person Karsten Gutjahr; 11.11.2013
comment
อืม คุณได้ลองส่งผลลัพธ์แล้วหรือยัง? (T)Foo42.FromBytes(Bytes, ref index)? - person Smeegs; 11.11.2013
comment
อืม ขอโทษที ฉันไม่สามารถช่วยได้มากกว่านี้ แต่ฉันไม่แน่ใจ. บางทีลองโพสต์คำถามนี้ใหม่ แต่ด้วยการไตร่ตรองในชื่อ คุณอาจดึงดูดกูรูแห่งการสะท้อนกลับ - person Smeegs; 11.11.2013
comment
ลองส่งไปที่ object จากนั้นไปที่ T: (T)((object)Foo42.FromBytes(Bytes, ref index)) - person gehho; 11.11.2013

คุณสามารถใช้ static FromBytes วิธีการทั่วไปในคลาสยูทิลิตี้ได้หรือไม่? แล้วทำอะไรแบบนี้ล่ะ?

listOf.Add(Utility.FromBytes<T>(bytes, ref index));

วิธีอรรถประโยชน์นั้นอาจจะดูน่าเกลียดนิดหน่อยถ้าแต่ละวิธี FromBytes แตกต่างกันมาก แต่ก็ไม่ได้แย่เกินไป มันเป็นโค้ดทั้งหมดที่เคยใช้งานในแต่ละคลาสอยู่แล้ว

บางสิ่งเช่นนี้:

public static class Utility
{
    public static T FromBytes<T>(byte[] bytes, ref int index)
    {
          if (typeof(T) is Foo1)
          {
               return Foo1.GetBytes(bytes, ref index);
          }
          //etc....
    }
}

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

person Kevin DiTraglia    schedule 11.11.2013
comment
ขอขอบคุณที่ให้ความสนใจ แต่อินเทอร์เฟซไม่สามารถมีวิธีการแบบคงที่ได้ คลาสนามธรรมไม่สามารถมีอะไรคงที่ได้ - person Karsten Gutjahr; 11.11.2013
comment
ฉันคิดว่าคุณจะต้องพึ่งพาการไตร่ตรอง - person Wagner DosAnjos; 11.11.2013
comment
น่าสนใจครับ ผมไม่ทราบจริงๆ ฉันเดาว่าการไตร่ตรองเป็นวิธีการทำเช่นนี้ - person Kevin DiTraglia; 11.11.2013
comment
นี่เป็นเพียงการเปลี่ยนคำถามเป็น: จะใช้ Utility.FromBytes ได้อย่างไร - person svick; 11.11.2013
comment
@svick ฉันจะจินตนาการถึงคำสั่งกรณีขนาดยักษ์ที่สะท้อนคำจำกัดความปัจจุบันของแต่ละคลาส (หรือเพียงแค่เรียกคลาสที่สร้างคำจำกัดความไว้แล้ว) - person Kevin DiTraglia; 11.11.2013
comment
ขออภัย ฉันไม่สามารถคืน Foo42 ได้ หากฉันควรจะคืน T การหล่อดูเหมือนจะเป็นไปไม่ได้ - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr อืมคนนี้ดูเหมือนจะยากกว่าที่ควรจะเป็น ฉันคิดว่าคุณสามารถส่งคืน object แทน T เป็นวิธีแก้ปัญหาชั่วคราวได้ ดูเหมือนว่าจะมีบางสิ่งที่หรูหรากว่านี้อยู่ด้วย - person Kevin DiTraglia; 11.11.2013
comment
@KarstenGutjahr นักแสดงเป็นไปได้ ต้องทำงานสักหน่อย แพงสำหรับประเภทมูลค่า และนี่คือวิธีที่คุณไป[return (T)(object)someType] - person Sriram Sakthivel; 11.11.2013

คุณสามารถใช้การสะท้อนกลับเพื่อค้นหาวิธีการในประเภทนั้น จากนั้นจึงสร้างผู้รับมอบสิทธิ์ให้ สิ่งที่ต้องการ:

delegate T FromBytesFunc<T>(byte[] bytes, ref int index);

public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
{
    FromBytesFunc<T> fromBytes =
        (FromBytesFunc<T>)Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>), typeof(T).GetMethod("FromBytes")

    var count = bytes[index++];
    var listOf = new ListOfFoo<T>();
    for (var i = 0; i < count; i++)
    {
        listOf.Add(fromBytes(bytes, ref index));
    }

    return listOf;
}
person svick    schedule 11.11.2013
comment
สิ่งนี้ดูน่าสนใจ แต่ 'MethodInfo' ไม่มีวิธีการ 'CreateDelegate()' ;-) - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr มันทำได้ แต่เฉพาะใน. Net 4.5 เท่านั้นฉันไม่ได้สังเกตเห็น - person svick; 11.11.2013

คุณสามารถลองสิ่งนี้:

sealed class RetType
{
    public object Value
    {
        get;
        private set;
    }

    public int Index
    {
        get;
        private set;
    }

    public RetType(object value, int index)
    {
        Value = value;
        Index = index;
    }
}

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    static readonly Dictionary<Type, Func<byte[], int, RetType>> dic = new Dictionary<Type, Func<byte[], int, RetType>>
    {
        {
            typeof(Foo1),
            new Func<byte[], int, RetType>((bytes, index) =>
            {
                var value = Foo1.FromBytes(bytes, ref index);

                return new RetType(value, index);
            })
        }
        // add here others Foo
    };

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOf = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            var o = dic[typeof(T)](bytes, index);

            listOf.Add((T)o.Value);

            index = o.Index;
        }

        return listOf;
    }
}

คุณสร้างการค้นหาเพื่อค้นหาวิธีการที่คุณต้องการเรียกใช้เพื่อสร้างอินสแตนซ์

person Alessandro D'Andria    schedule 11.11.2013
comment
คุณกำลังบอกว่าต้องกรอกพจนานุกรมด้วยตนเองสำหรับ Foo แต่ละตัวใช่ไหม นั่นฟังดูไม่ใช่วิธีแก้ปัญหาที่ดีนัก - person svick; 11.11.2013
comment
แน่นอนและฉันยอมรับว่ามันไม่ใช่วิธีแก้ปัญหาที่ดี แต่มันคือวิธีแก้ปัญหา ตัวเลือกที่ชัดเจนอีกทางหนึ่งคือการสะท้อนตามที่เสนอข้างต้น - person Alessandro D'Andria; 11.11.2013
comment
ฉันสนใจวิธีแก้ปัญหาที่ไม่ค่อยดีนัก แต่สำหรับคอมไพเลอร์ ´T´ ของ `RetType‹T›´ นั้นไม่เหมือนกับ ´T´ เช่นเดียวกับใน `ListOfFoo‹T›´ เพียงแค่ใช้ชื่อเดียวกัน ดังนั้นคอมไพเลอร์จึงไม่สามารถส่งอันหนึ่งไปยังอีกอันหนึ่งได้ ... :-( - person Karsten Gutjahr; 11.11.2013
comment
@ KarstenGutjahr ตกลงฉันแก้ไขโค้ดใหม่และทดสอบแล้ว ลองดูสิ - person Alessandro D'Andria; 11.11.2013
comment
ดีเลย ดูสมเหตุสมผลดี ฉันแค่เกรงว่าโค้ดนี้สามารถย่อลงเหลือ (T)((object)Foo42.FromBytes(Bytes, ref index)) ซึ่งถูกแสดงความคิดเห็นที่นี่ 20 นาทีหลังจากความคิดเห็นของคุณ - person Karsten Gutjahr; 11.11.2013
comment
ฉันแน่ใจว่าการกรอกพจนานุกรมสามารถดำเนินการได้โดยอัตโนมัติ: foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == "Foos")) { Dict.Add(type, (FromBytesFunc<T>)Delegate.CreateDelegate(typeof(FromBytesFunc<T>), type.GetMethod("FromBytes"))); } ด้วย private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index); แต่คุณไม่มีการตรวจสอบคอมไพเลอร์ - person Karsten Gutjahr; 12.11.2013

ไม่แน่ใจว่าสิ่งนี้เหมาะกับคุณหรือไม่ แต่เป็นตัวเลือก:

  1. กำหนดอินเทอร์เฟซ IHaveFromBytesMethod<T> ซึ่งกำหนดวิธีการอินสแตนซ์ FromBytes(...)
  2. ทำให้ Foo# ประเภททั้งหมดใช้อินเทอร์เฟซนี้ (หากคุณไม่ต้องการให้วิธีการอินสแตนซ์นี้มองเห็นได้ในทันที คุณสามารถปรับใช้อินเทอร์เฟซได้อย่างชัดเจน)
  3. การใช้งานวิธีการอินสแตนซ์ทั้งหมดเพียงส่งต่อการเรียกไปยังวิธีการคงที่ของประเภทนั้น
  4. จำกัดพารามิเตอร์ประเภท T ในคลาส ListOfFoo<T> ของคุณเพื่อใช้อินเทอร์เฟซนี้ และเพื่อจัดเตรียมตัวสร้างแบบไม่มีพารามิเตอร์
  5. เมื่อคุณต้องการวิธีการคงที่ ให้สร้างอ็อบเจ็กต์จำลองใหม่ประเภท T โดยใช้ตัวสร้างแบบไม่มีพารามิเตอร์ จากนั้นเรียกใช้เมธอดอินสแตนซ์บนอินสแตนซ์จำลองนั้น

อินเตอร์เฟซ:

public interface IHaveFromBytesMethod<T>
{
    T FromBytes(byte[] bytes, ref int index);
}

หนึ่งในคลาส Foo#:

public class Foo1 : IHaveFromBytesMethod<Foo1>
{
    public Foo1()
    {
        // ...
    }

    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // ...
    }

    public Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // within the instance method simply call the static method
        return Foo1.FromBytes(bytes, ref index);
    }
}

คลาส ListOfFoo<T> ที่แก้ไขแล้ว:

// requires T to implement the interface and provide a parameterless ctor!
public class ListOfFoo<T> : System.Collections.Generic.List<T>
    where T : IHaveFromBytesMethod<T>, new()
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        // create dummy instance for accessing static method via instance method
        T dummy = new T();
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            // instead of calling the static method,
            // call the instance method on the dummy instance
            listOfFoo.Add(dummy.FromBytes(bytes, ref index));
        }

        return listOfFoo;
    }
}
person gehho    schedule 11.11.2013
comment
นั่นน่าเกลียดจริงๆ ตามหลักเหตุผลแล้ว มันไม่สมเหตุสมผลเลยที่จะสร้างอินสแตนซ์ของ Foo เพื่อสร้างอินสแตนซ์อื่นของมัน - person svick; 11.11.2013
comment
มันเป็นความจริง. แต่วิธีแก้ปัญหาทั้งหมดในหน้านี้ไม่ได้น่าเกลียดในทางใดทางหนึ่งใช่หรือไม่ ฉันแค่อยากเสนอทางเลือกอื่นที่ทำงานได้โดยไม่ต้องไตร่ตรอง - person gehho; 11.11.2013

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

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    private static readonly FromBytesFunc<T> BytesFromFunc = 
        (FromBytesFunc<T>)System.Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>),
            typeof(T).GetMethod("FromBytes"));

    private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index);

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(BytesFromFunc(bytes, ref index));
        }

        return listOfFoo;
    }
}
person Karsten Gutjahr    schedule 12.11.2013

นี่จะเป็นบทสรุปสำหรับผู้ชมในอนาคตทั้งหมด

เนื่องจากคุณไม่สามารถเรียกใช้เมธอดได้ ปัญหาหลักคือการรับผู้รับมอบสิทธิ์ มีสองแนวทาง:

  • ผ่านการสะท้อน
  • จากผู้โทร

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

การตรวจสอบเวลาคอมไพล์เป็นปัญหา หากคุณใส่ใจมากเกี่ยวกับการตรวจสอบเวลาคอมไพล์ คุณควรส่งผู้รับมอบสิทธิ์จากผู้โทร หากคุณสนใจมากขึ้นเกี่ยวกับการโทรแบบ clean คุณจะต้องเสียสละการตรวจสอบเวลาคอมไพล์

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

person Karsten Gutjahr    schedule 28.03.2014