এই স্ট্রিং এক্সটেনশন পদ্ধতিটি কেন ব্যতিক্রম ছুঁড়ে না?


119

আমি একটি সি # স্ট্রিং এক্সটেনশন পদ্ধতি পেয়েছি যা IEnumerable<int>স্ট্রিংয়ের মধ্যে একটি স্ট্রিংয়ের সমস্ত সূচকগুলির একটি ফেরত দেওয়া উচিত । এটি তার উদ্দেশ্যযুক্ত উদ্দেশ্যে পুরোপুরি কাজ করে এবং প্রত্যাশিত ফলাফলগুলি ফিরে আসে (আমার পরীক্ষার মধ্যে একটি দ্বারা প্রমাণিত, যদিও এটি নীচের এক নয়) তবে অন্য ইউনিট পরীক্ষাটি এটি নিয়ে একটি সমস্যা আবিষ্কার করেছে: এটি নাল আর্গুমেন্টগুলি পরিচালনা করতে পারে না।

আমি যে এক্সটেনশন পদ্ধতিটি পরীক্ষা করছি তা এখানে:

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
    if (searchText == null)
    {
        throw new ArgumentNullException("searchText");
    }
    for (int index = 0; ; index += searchText.Length)
    {
        index = str.IndexOf(searchText, index);
        if (index == -1)
            break;
        yield return index;
    }
}

এখানে পরীক্ষাটি সমস্যাটিকে চিহ্নিত করেছিল:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Extensions_AllIndexesOf_HandlesNullArguments()
{
    string test = "a.b.c.d.e";
    test.AllIndexesOf(null);
}

যখন পরীক্ষাটি আমার এক্সটেনশন পদ্ধতির বিপরীতে চলে, তখন স্ট্যান্ডার্ড ত্রুটি বার্তার সাথে এটি ব্যর্থ হয় যে পদ্ধতিটি "একটি ব্যতিক্রম ছুঁড়ে ফেলেনি"।

এটি বিভ্রান্তিকর: আমি স্পষ্টভাবে nullফাংশনে প্রবেশ করেছি, তবুও কোনও কারণে তুলনা null == nullফিরে আসছে false। অতএব, কোনও ব্যতিক্রম নিক্ষেপ করা হয় না এবং কোড অব্যাহত থাকে।

আমি নিশ্চিত করেছি যে এটি পরীক্ষার সাথে কোনও বাগ নয়: Console.WriteLineনাল-তুলনা ifব্লকে আমার মূল প্রকল্পে কলটি চালানোর সময়, কনসোলে কিছুই দেখানো হয় না এবং catchআমার যুক্ত হওয়া কোনও ব্লকের ব্যতিক্রমও ধরা পড়ে না । তদতিরিক্ত, string.IsNullOrEmptyপরিবর্তে ব্যবহার করা == nullএকই সমস্যা আছে।

কেন এই অনুমিত-সহজ তুলনা ব্যর্থ হয়?


5
আপনি কোডটি দিয়ে পদক্ষেপের চেষ্টা করেছেন? এটি সম্ভবত এটি খুব দ্রুত সমাধান করা হবে।
ম্যাথু হগেন

1
কি হয় ? (এটি কি একটি ব্যতিক্রম ছুঁড়ে
ফেলেছে

@ ব্যবহারকারী ২2828647৪০ যা ঘটেছিল তা আমি বর্ণনা করেছি। কোনও ব্যতিক্রম নেই, কেবলমাত্র একটি ব্যর্থ পরীক্ষা এবং একটি রান পদ্ধতি।
আর্টঅফকোড


2
আপনাকে স্বাগতম. এটি জনের "খারাপতম গোটচা" তালিকাও তৈরি করেছে: stackoverflow.com/a/241180/88656 । এটি বেশ সাধারণ সমস্যা।
এরিক লিপার্ট

উত্তর:


158

আপনি ব্যবহার করছেন yield return। এটি করার সময়, সংকলকটি আপনার পদ্ধতিটিকে এমন একটি ফাংশনে পুনর্লিখন করবে যা একটি উত্পাদিত শ্রেণি ফেরত দেয় যা একটি রাষ্ট্রের মেশিন প্রয়োগ করে।

বিস্তৃতভাবে বলতে গেলে, এটি স্থানীয়দের সেই শ্রেণীর ক্ষেত্রগুলিতে পুনর্লিখন করে এবং yield returnনির্দেশগুলির মধ্যে আপনার অ্যালগরিদমের প্রতিটি অংশই একটি রাজ্যে পরিণত হয়। সংকলনের পরে এই পদ্ধতিটি কী হয়ে যায় আপনি একটি ডিকম্পিলার দিয়ে পরীক্ষা করতে পারেন (স্মার্ট ডিসকোপিলেশনটি যা উত্পাদন করবে তা বন্ধ করে দেওয়ার বিষয়টি নিশ্চিত করুন yield return)।

তবে নীচের লাইনটি হল: আপনি পুনরাবৃত্তি শুরু না করা পর্যন্ত আপনার পদ্ধতির কোডটি কার্যকর করা হবে না।

পূর্বশর্তগুলি পরীক্ষা করার স্বাভাবিক উপায় হ'ল আপনার পদ্ধতিটি দুটি ভাগে ভাগ করা:

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
    if (str == null)
        throw new ArgumentNullException("str");
    if (searchText == null)
        throw new ArgumentNullException("searchText");

    return AllIndexesOfCore(str, searchText);
}

private static IEnumerable<int> AllIndexesOfCore(string str, string searchText)
{
    for (int index = 0; ; index += searchText.Length)
    {
        index = str.IndexOf(searchText, index);
        if (index == -1)
            break;
        yield return index;
    }
}

এটি কাজ করে কারণ প্রথম পদ্ধতিটি আপনার প্রত্যাশার মতোই আচরণ করবে (তাত্ক্ষণিক কার্যকরকরণ), এবং দ্বিতীয় পদ্ধতি দ্বারা প্রয়োগকৃত রাষ্ট্রীয় যন্ত্রটি ফিরিয়ে দেবে।

মনে রাখবেন যে আপনার strজন্য প্যারামিটারটিও পরীক্ষা করা উচিত null, কারণ এক্সটেনশন পদ্ধতিগুলি মানগুলিতে কল করা যেতে পারে null, কারণ তারা কেবল সিনট্যাকটিক চিনি।


আপনার কোডটিতে সংকলকটি কী করে সে সম্পর্কে যদি আপনি কৌতূহল থেকে থাকেন তবে আপনার পদ্ধতিটি এখানে শট কম্পাইলার-জেনারেট কোড বিকল্পটি বিকল্পটি ব্যবহার করে ডটপিকের সাথে সংক্ষেপিত ।

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
  Test.<AllIndexesOf>d__0 allIndexesOfD0 = new Test.<AllIndexesOf>d__0(-2);
  allIndexesOfD0.<>3__str = str;
  allIndexesOfD0.<>3__searchText = searchText;
  return (IEnumerable<int>) allIndexesOfD0;
}

[CompilerGenerated]
private sealed class <AllIndexesOf>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
  private int <>2__current;
  private int <>1__state;
  private int <>l__initialThreadId;
  public string str;
  public string <>3__str;
  public string searchText;
  public string <>3__searchText;
  public int <index>5__1;

  int IEnumerator<int>.Current
  {
    [DebuggerHidden] get
    {
      return this.<>2__current;
    }
  }

  object IEnumerator.Current
  {
    [DebuggerHidden] get
    {
      return (object) this.<>2__current;
    }
  }

  [DebuggerHidden]
  public <AllIndexesOf>d__0(int <>1__state)
  {
    base..ctor();
    this.<>1__state = param0;
    this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
  }

  [DebuggerHidden]
  IEnumerator<int> IEnumerable<int>.GetEnumerator()
  {
    Test.<AllIndexesOf>d__0 allIndexesOfD0;
    if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
    {
      this.<>1__state = 0;
      allIndexesOfD0 = this;
    }
    else
      allIndexesOfD0 = new Test.<AllIndexesOf>d__0(0);
    allIndexesOfD0.str = this.<>3__str;
    allIndexesOfD0.searchText = this.<>3__searchText;
    return (IEnumerator<int>) allIndexesOfD0;
  }

  [DebuggerHidden]
  IEnumerator IEnumerable.GetEnumerator()
  {
    return (IEnumerator) this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
  }

  bool IEnumerator.MoveNext()
  {
    switch (this.<>1__state)
    {
      case 0:
        this.<>1__state = -1;
        if (this.searchText == null)
          throw new ArgumentNullException("searchText");
        this.<index>5__1 = 0;
        break;
      case 1:
        this.<>1__state = -1;
        this.<index>5__1 += this.searchText.Length;
        break;
      default:
        return false;
    }
    this.<index>5__1 = this.str.IndexOf(this.searchText, this.<index>5__1);
    if (this.<index>5__1 != -1)
    {
      this.<>2__current = this.<index>5__1;
      this.<>1__state = 1;
      return true;
    }
    goto default;
  }

  [DebuggerHidden]
  void IEnumerator.Reset()
  {
    throw new NotSupportedException();
  }

  void IDisposable.Dispose()
  {
  }
}

এটি অবৈধ সি # কোড, কারণ সংকলকটি ভাষাকে অনুমতি দেয় না এমন কাজ করার অনুমতি দেওয়া হয় তবে আইএল-এ বৈধ - উদাহরণস্বরূপ ভেরিয়েবলের নামকরণ এমনভাবে করা যাতে আপনি নাম সংঘর্ষ এড়াতে পারবেন না।

তবে আপনি দেখতে পাচ্ছেন, AllIndexesOfকেবলমাত্র একটি অবজেক্ট তৈরি করে এবং ফেরত দেয়, যার নির্মাতা কেবল কিছু স্থিতি শুরু করে। GetEnumeratorকেবল বস্তুটি অনুলিপি করে। আসল কাজটি করা হয় যখন আপনি গণনা শুরু করেন ( MoveNextপদ্ধতিটি কল করে )।


9
বিটিডাব্লু, আমি উত্তরে নিম্নলিখিত গুরুত্বপূর্ণ পয়েন্টটি যুক্ত করেছি: নোট করুন যে আপনার strজন্য প্যারামিটারটিও পরীক্ষা করা উচিত null, কারণ এক্সটেনশন পদ্ধতিগুলি nullমানগুলিতে কল করা যেতে পারে , কারণ তারা কেবল সিনট্যাকটিক চিনি।
লুকাস ট্রাজেনিউস্কি

2
yield returnনীতিগতভাবে এটি একটি দুর্দান্ত ধারণা, তবে এটিতে অনেকগুলি অদ্ভুত গোটচাস রয়েছে। এটি আলোকিত করার জন্য ধন্যবাদ!
নাটায়রভিন 12'15

সুতরাং, মূলত কোনও পূর্বানুরূপে, যদি চালক চালানো হয় তবে একটি ত্রুটি নিক্ষেপ করা হবে?
এমভিসিডিএস

1
@ এমভিসিডিএস ঠিক কনস্ট্রাক্ট MoveNextদ্বারা ফণা অধীন বলা হয় foreach। আমি কি একটি ব্যাখ্যা লিখেছিলেন foreachমধ্যে আছে আমার উত্তর সংগ্রহ শব্দার্থবিদ্যা ব্যাখ্যা যদি আপনি সঠিক প্যাটার্ন দেখতে চাই।
লুকাস ট্রজেসনিউস্কি

34

আপনার কাছে একটি পুনরুদ্ধারকারী ব্লক রয়েছে। সেই পদ্ধতিতে থাকা কোডের MoveNextকোনওটিই ফেরত পুনরাবৃত্তকারীকে কল করার বাইরে চলে না। পদ্ধতিটি কল করা লক্ষ করা যায় না তবে রাষ্ট্রীয় মেশিন তৈরি করে এবং এটি কখনই ব্যর্থ হয় না (চরমের বাইরে যেমন মেমরির ত্রুটি, স্ট্যাক ওভারফ্লোস বা থ্রেড অ্যাওর্ট ব্যতিক্রম)।

আপনি যখন ক্রমটি পুনরাবৃত্তি করার চেষ্টা করবেন তখন ব্যতিক্রমগুলি পাবেন।

এই কারণেই লিনকিউ পদ্ধতিগুলিতে ত্রুটিটি পরিচালনা করার জন্য তারা চাইছেন ত্রুটিগুলি পরিচালনা করতে আসলে দুটি পদ্ধতি দরকার। তাদের একটি ব্যক্তিগত পদ্ধতি রয়েছে যা একটি পুনরুদ্ধারকারী ব্লক, এবং তারপরে একটি নন-ইটারেটর ব্লক পদ্ধতি যা যুক্তি যাচাইকরণ ব্যতীত কিছুই করে না (যাতে এটি স্থগিতের চেয়ে তা অধীর আগ্রহে করা যায়) অন্য সমস্ত কার্যকারিতা স্থগিত করার সময়।

সুতরাং এটি সাধারণ প্যাটার্ন:

public static IEnumerable<T> Foo<T>(
    this IEnumerable<T> souce, Func<T, bool> anotherArgument)
{
    //note, not an iterator block
    if(anotherArgument == null)
    {
        //TODO make a fuss
    }
    return FooImpl(source, anotherArgument);
}

private static IEnumerable<T> FooImpl<T>(
    IEnumerable<T> souce, Func<T, bool> anotherArgument)
{
    //TODO actual implementation as an iterator block
    yield break;
}

0

অন্যরা যেমন বলেছে, গণনা করা হবে ততক্ষণ পর্যন্ত তারা গণনা করা শুরু করবে না (যেমন IEnumerable.GetNextপদ্ধতিটি বলা হয়)। এইভাবে

List<int> indexes = "a.b.c.d.e".AllIndexesOf(null).ToList<int>();

যতক্ষণ না আপনি গণনা শুরু করেন, ততক্ষণ মূল্যায়ন করা হবে না

foreach(int index in indexes)
{
    // ArgumentNullException
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.