วิธีการเปรียบเทียบค่าสถานะใน C #


155

ฉันมีธง enum ด้านล่าง

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

ฉันไม่สามารถทำให้คำสั่ง if ประเมินว่าเป็นจริง

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

ฉันจะทำให้เรื่องนี้เป็นจริงได้อย่างไร


แก้ไขให้ถูกต้องหากฉันผิด 0 เหมาะสมที่จะใช้เป็นค่าสถานะหรือไม่
Roy Lee

4
@Roylee: 0 ยอมรับได้และเป็นความคิดที่ดีที่จะมีการตั้งค่าสถานะ "ไม่มี" หรือ "ไม่ได้กำหนด" เพื่อทดสอบว่าไม่มีการตั้งค่าสถานะ มันไม่ได้จำเป็น แต่เป็นวิธีปฏิบัติที่ดี สิ่งสำคัญที่ต้องจำเกี่ยวกับเรื่องนี้คือคำตอบของเขาโดย Leonid
Andy

5
@Roylee Microsoft ขอแนะนำให้ใช้Noneค่าสถานะเป็นศูนย์ ดูmsdn.microsoft.com/en-us/library/vstudio/ ......
ThatMatthew

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

คุณอยู่ค่อนข้างใกล้เคียงยกเว้นคุณจะต้องกลับคุณตรรกะคุณต้องบิต&ประกอบการเปรียบเทียบ|ก็เหมือนนอกจากนี้: 1|2=3, 5|2=7, 3&2=2, ,7&2=2 ประเมินทุกอย่างอื่นไป 8&2=00falsetrue
Damian Vogel

คำตอบ:


321

ใน .NET 4 มีวิธีการใหม่Enum.HasFlag อนุญาตให้คุณเขียน:

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

ซึ่งสามารถอ่านได้มากขึ้น IMO

แหล่งที่มา. NET ระบุว่าสิ่งนี้ดำเนินการกับตรรกะเดียวกันกับคำตอบที่ยอมรับได้:

public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
                "Argument_EnumTypeDoesNotMatch", 
                flag.GetType(), 
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag); 
}

23
ในที่สุดก็มีอะไรบางอย่างออกมาจากกล่อง นี่เยี่ยมมากฉันรอฟังก์ชั่นนี้ค่อนข้างง่ายมานานแล้ว ดีใจที่พวกเขาตัดสินใจที่จะลื่นมัน
Rob van Groenewoud

9
อย่างไรก็ตามหมายเหตุคำตอบด้านล่างนี้แสดงปัญหาด้านประสิทธิภาพด้วยวิธีนี้ซึ่งอาจเป็นปัญหาสำหรับบางคน ไม่มีความสุขสำหรับฉัน
Andy Mortimer

2
การพิจารณาประสิทธิภาพสำหรับวิธีนี้คือการชกมวยเพราะมันใช้อาร์กิวเมนต์เป็นตัวอย่างของEnumคลาส
Adam Houldsworth

1
สำหรับข้อมูลเกี่ยวกับปัญหาด้านประสิทธิภาพให้ดูที่คำตอบนี้: stackoverflow.com/q/7368652/200443
Maxence

180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) เป็นบิตและการดำเนินการ

FlagTest.Flag1เทียบเท่า001กับ enum ของ OP ทีนี้สมมุติว่าtestItemมี Flag1 และ Flag2 (มันคือ bitwise 101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
ตรรกะตรงนี้คืออะไร? เพรดิเคตทำไมต้องเขียนเช่นนี้
Ian R. O'Brien

4
@ IanR.O'Brien Flag1 | ค่าสถานะ 2 แปลเป็น 001 หรือ 010 ซึ่งเหมือนกับ 011 ตอนนี้ถ้าคุณทำอย่างเท่าเทียมกันของ 011 == ตั้งค่าสถานะ 1 หรือแปล 011 == 001 นั่นจะส่งกลับค่าเท็จเสมอ ทีนี้ถ้าคุณทำ bitwise และกับ Flag1 แล้วมันแปลเป็น 011 และ 001 ที่คืน 001 ตอนนี้การทำ equality คืนค่าจริงเพราะ 001 == 001
pqsk

มันเป็นทางออกที่ดีที่สุดเนื่องจาก HasFlags ใช้ทรัพยากรมากขึ้น และยิ่งกว่านั้นทุกสิ่งที่ HasFlags กำลังทำอยู่นี่คือสิ่งที่ทำโดยคอมไพเลอร์
Sebastian Xawery Wiawniowiecki

78

สำหรับผู้ที่มีปัญหาในการมองเห็นสิ่งที่เกิดขึ้นกับวิธีแก้ปัญหาที่ยอมรับได้

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (ตามคำถาม) หมายถึง

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

จากนั้นในประโยค if ด้านซ้ายมือของการเปรียบเทียบคือ

(testItem & flag1) 
 = (011 & 001) 
 = 001

และคำสั่ง if แบบเต็ม (ที่ประเมินเป็นจริงหากflag1ตั้งค่าไว้testItem)

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ ฟิล-เวนีย์

โปรดทราบว่ายกเว้นในกรณีที่ง่ายที่สุดEnum.HasFlagมีบทลงโทษที่หนักหน่วงเมื่อเปรียบเทียบกับการเขียนโค้ดด้วยตนเอง พิจารณารหัสต่อไปนี้:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

การทำซ้ำกว่า 10 ล้านครั้งวิธีการขยาย HasFlags ใช้เวลามากถึง 4793 ms เทียบกับ 27 ms สำหรับการใช้งานระดับบิตมาตรฐาน


5
จริง หากคุณดูการใช้งานของ HasFlag คุณจะเห็นว่ามันเป็น "GetType ()" ในตัวถูกดำเนินการทั้งสองซึ่งค่อนข้างช้า จากนั้นจะใช้ "Enum.ToUInt64 (value.GetValue ());" ในตัวถูกดำเนินการทั้งสองก่อนที่จะทำการตรวจสอบระดับบิต
276648

1
ฉันลองทดสอบของคุณหลาย ๆ ครั้งและได้รับประมาณ 500ms สำหรับ HasFlags และประมาณ 32ms สำหรับระดับบิต ในขณะที่ยังคงมีลำดับความสำคัญเร็วขึ้นด้วย bitwise HasFlags เป็นลำดับความสำคัญลดลงจากการทดสอบของคุณ (ทดสอบการใช้งานกับ 2.5GHz Core i3 และ. NET 4.5)
MarioVW

1
@MarioVW ทำงานหลาย ๆ ครั้งบน. NET 4, i7-3770 ให้ ~ 2400ms เทียบกับ ~ 20ms ในโหมด AnyCPU (64 บิต) และ ~ 3000ms เทียบกับ ~ 20ms ในโหมด 32 บิต .NET 4.5 อาจปรับให้เหมาะสมเล็กน้อย นอกจากนี้ให้สังเกตความแตกต่างของประสิทธิภาพระหว่างการสร้าง 64- บิตและ 32- บิตซึ่งอาจเป็นผลมาจากการคำนวณทางคณิตศาสตร์ที่เร็วกว่า 64 บิต (ดูความคิดเห็นแรก)
Bob

1
(«flag var» & «flag value») != 0ไม่ได้ผลสำหรับฉัน สภาพมักจะล้มเหลวและคอมไพเลอร์ของฉัน (Unity3D โมโน 2.6.5) รายงาน“CS0162 เตือน: รหัสไม่สามารถเข้าถึงได้พบ”if (…)เมื่อนำมาใช้ใน
Slipp D. Thompson

1
@ wraith808: ฉันรู้ว่าผิดถูกก็ทำให้มีการทดสอบของฉันว่าคุณมีที่ถูกต้องใน yours- อำนาจของ 2 … = 1, … = 2, … = 4กับค่า enum [Flags]ที่มีความสำคัญอย่างยิ่งเมื่อใช้ ฉันสมมติว่ามันจะเริ่มต้นรายการ1และล่วงหน้าโดย Po2s โดยอัตโนมัติ พฤติกรรมดูสอดคล้องกันใน MS .NET และ Mono ของ Unity ฉันขอโทษที่ทำให้คุณ
Slipp D. Thompson

21

ฉันจะตั้งค่าวิธีการขยายไปทำมันคำถามที่เกี่ยวข้อง

โดยทั่วไป:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

จากนั้นคุณสามารถทำได้:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

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


นี่ควรเป็นความคิดเห็น แต่เนื่องจากฉันเป็นผู้ใช้ใหม่ดูเหมือนว่าฉันไม่สามารถเพิ่มความคิดเห็นได้ ... public static bool IsSet (อินพุต Enum นี้ Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } มีวิธีที่จะเข้ากันได้กับ enum ชนิดใด (เพราะที่นี่มันจะไม่ทำงานถ้า enum ของคุณเป็นประเภท UInt64 หรืออาจมีค่าลบ)
276648

ค่อนข้างซ้ำซ้อนกับ Enum.HasFlag (Enum) (มีให้ใน. net 4.0)
PPC

1
@PPC ฉันจะไม่พูดซ้ำซ้อนอย่างแน่นอน - ผู้คนมากมายกำลังพัฒนาบนเฟรมเวิร์กเวอร์ชันเก่า คุณพูดถูกผู้ใช้. Net 4 ควรใช้HasFlagส่วนขยายแทน
Keith

4
@ Keith: นอกจากนี้ยังมีความแตกต่างที่โดดเด่น: ((ทดสอบสถานะ) 0x1) .Flag (0x0) จะกลับมาจริงซึ่งอาจหรืออาจจะเป็นพฤติกรรมที่ต้องการ
PPC

19

คำแนะนำอีกอย่างหนึ่ง ... อย่าทำการตรวจสอบไบนารี่มาตรฐานด้วยค่าสถานะซึ่งมีค่าเป็น "0" เช็คของคุณเกี่ยวกับธงนี้จะเป็นจริงเสมอ

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

หากคุณตรวจสอบไบนารีพารามิเตอร์การป้อนข้อมูลกับ FullInfo - คุณจะได้รับ:

detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez จะเป็นจริงเสมอในทุกสิ่ง & 0 เสมอ == 0


แต่คุณควรตรวจสอบว่าค่าของอินพุตเป็น 0:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);

ฉันเพิ่งแก้ไขข้อผิดพลาด 0 ธง ฉันคิดว่านี่เป็นข้อผิดพลาดในการออกแบบใน. NET Framework (3.5) เพราะคุณจำเป็นต้องรู้ว่าค่าสถานะใดเป็น 0 ก่อนที่จะทำการทดสอบ
thersch


5

สำหรับการทำงานของบิตคุณต้องใช้ตัวดำเนินการระดับบิต

สิ่งนี้ควรทำเคล็ดลับ:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

แก้ไข: แก้ไขเช็คของฉัน - ฉันเลื่อนกลับไปยังวิธี C / C ++ ของฉัน (ขอบคุณ Ryan Farley สำหรับการชี้ให้เห็น)


5

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

กล่าวคือ

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

4

ลองสิ่งนี้:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
โดยพื้นฐานแล้วโค้ดของคุณจะถามว่าการตั้งค่าสถานะทั้งสองนั้นเหมือนกันหรือไม่กับการตั้งค่าสถานะหนึ่งชุดซึ่งเป็นเท็จอย่างเห็นได้ชัด รหัสด้านบนจะปล่อยให้มีการตั้งค่าบิต Flag1 เท่านั้นหากมีการตั้งค่าเลยจากนั้นเปรียบเทียบผลลัพธ์นี้กับ Flag1


1

แม้จะไม่มี [ธง] คุณสามารถใช้สิ่งนี้

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

หรือถ้าคุณมีค่าเป็นศูนย์ enum

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.