Как вернуть массив в процедуре обратно в основную процедуру в C#? - PullRequest
0 голосов
/ 08 мая 2020
    static string readfileName(string[] name)
    {

        using (StreamReader file = new StreamReader("StudentMarks.txt"))
        {
            int counter = 0;
            string ln;

            while ((ln = file.ReadLine()) != null)
            {
                if (ln.Length > 4)
                {
                    name[counter] = ln;
                    counter++;

                }
            }

            file.Close();
            return name;
        }
    }

Это процедура, которую я сейчас пытаюсь вернуть массив name[50], но ошибка времени компиляции Я не могу исправить состояния

"Ошибка CS0029 Невозможно неявно преобразовать тип 'string []' в 'string' "

Ответы [ 4 ]

1 голос
/ 08 мая 2020

Не нужно. Ваш основной метод передал массив этому методу, этот метод заполнил его. Ему не нужно возвращать его, потому что объект, на который указывает ваша переменная name, является тем же объектом, на который указывает исходная переменная в основном методе; в вашем основном методе уже есть все данные массива:

static void Main(){

  var x = new string[10];

  MyMethod(x);

  Console.Write(x[0]); //prints "Hello"
}

static void MyMethod(string[] y){

  y[0] = "Hello";

}

В этом демонстрационном коде выше мы начинаем с массива размером 10, на который ссылается переменная x. В памяти это выглядит так:

x --refers to--> arraydata

Когда вы вызываете MyMethod и передаете x, c# создаст другую ссылку y, которая указывает на те же данные:

x --refers to--> arraydata <--refers to-- y

Теперь, потому что обе ссылки указывают на одну и ту же область памяти. Все, что вы делаете с y, также влияет на то, что видит x. Вы помещаете строку (как я сделал с Hello) в слот 0, и x, и y ее видят. Когда MyMethod завершает работу, ссылка y отбрасывается, но x сохраняется и видит все изменения, внесенные вами при работе с y

Единственное, что вы не можете сделать, это указать y на другой объект массива в другом месте в памяти. Это не изменит x. Вы не можете этого сделать:

static void MyMethod(string[] y){

    y = new string[20];

}

Если вы сделаете это, ваша полезная ссылка на x и y, указывающая на одну и ту же область памяти:

x ---> array10 <--- y

изменится на:

x ---> array10       y ---> array20

И тогда весь array20 и ссылка y будут отброшены, когда MyMethod завершится.

То же правило применяется, если вы вызываете метод, который предоставляет вам массив:

static void MyMethod(string[] y){

    y = File.ReadAllLines("some path"); //this also points y away to a new array made by ReadAllLines

}

Не имеет значения, как и кто создает новый массив. Просто помните, что вы можете возиться с содержимым объекта, на который указывает y, сколько угодно, и изменения будут видны x, но вы не можете изменить весь объект, на который указывает y, и надеяться x увидит это

, в этом случае вам нужно будет вернуть его, когда вы закончите:

static string[] MyMethod(string[] y){
  y = new ... 

  return y;
}

И основной метод должен будет зафиксировать изменение:

Main(...){

  string[] x = new string[10];

  string[] result = MyMethod(x);

}

Сейчас, пока я провожу этот мини-урок по «передаче по ссылке» и «передаче по значению» (который должен был называться «передача по исходной ссылке» и «передача по копии ссылки» ) было бы полезно отметить, что - это способ что-то изменить, чтобы MyMethod мог заменить y на совершенно новый объект, и x тоже увидит изменение.

На самом деле мы этим никогда не пользуемся; в этом редко возникает необходимость. Почти единственный раз он используется в таких вещах, как int.Parse. Я говорю вам для полноты, если образование, чтобы, если вы столкнетесь с этим, вы это поймете, но вы всегда должны предпочесть подход «изменить содержимое, но не весь объект» или подход «если вы заставляете новый объект передавать его обратно»

Помечая аргумент y ключевым словом ref, c# не будет делать копию ссылки при вызове метода, он будет использовать исходную ссылку и временно позволит вам назвать ее y:

static void MyMethod(ref string[] y){
    y = new array[20];
}

Наша диаграмма:

x ---> array10data

Временно становится:

x a.k.a y ---> array10data

Итак, если вы указываете y на новый массив, x тоже претерпевает изменение, потому что это одна и та же ссылка ; y больше не является другой ссылкой на одни и те же данные

x a.k.a y ---> array20data

Как я уже сказал, не используйте его - мы всегда стараемся избегать его по разным причинам.


Итак, я сказал в начале «вам не нужно» - под этим, и по указанным выше причинам я имел в виду , вам не нужно ничего возвращать из этого метода

Ваш метод получает массив, который он должен заполнить (из файла) в качестве параметра; он нигде не создает новый массив, поэтому нет необходимости возвращать массив по завершении. Он просто поместит любую строку длиной более 4 символов в слот массива. Затем он может завершить sh, ничего не возвращая, и метод, который вызвал этот метод, увидит изменения, внесенные им в массив. Это похоже на мой код, где MyMethod изменяет слот 0 в массиве, MyMethod был объявлен как void, поэтому ему не нужно было делать оператор возврата, и мой бог основного метода все еще мог видеть Hello, которое я вставил в массив. Точно так же ваш метод Main будет видеть все эти строки из файла, если вы создадите свой метод ReadFileName (который, возможно, следует назвать FillArray), потому что он заполняет массив с именем name

Самое полезное, что ваша метод может возвращать целое число, указывающее, сколько строк было прочитано; переданный массив имеет фиксированный размер. Вы не можете изменить его размер, потому что это влечет за собой создание нового массива, который не будет работать по всем тем причинам, о которых я говорил выше. Если бы вы создали новый массив и вернули его, в передаче массива не было бы никакого смысла.

Таким образом, есть несколько способов улучшить этот код, но, на мой взгляд, их всего два:

  • не передавать массив; пусть этот метод создаст новый массив и вернет его. Новый переданный массив может иметь точный размер, чтобы соответствовать
  • с учетом идеи «передать массив в» и вернуть целое число, показывающее, сколько строк было фактически прочитано вместо

Для Вторая идея (которая является самой простой для реализации) вам нужно изменить тип возвращаемого значения на int:

static int ReadFileName(string[] name)

И вы должны вернуть ту переменную, которую вы используете, чтобы отслеживать, в какой слот вставить следующую вещь, counter . Счетчик всегда на 1 больше, чем количество сохраненных вами вещей, поэтому:

return counter - 1;

Теперь ваш метод вызова может выглядеть так:

string[] fileData = new string[10000]; //needs to be big enough to hold the whole file!
int numberOfLinesRead = ReadFileName(fileData);

Теперь вы понимаете, почему ReadFileName является плохая репутация метода? Было бы лучше назвать его FillArrayFromFile. Эта последняя строка кода не читается как книга, она не имеет смысла с точки зрения естественного языка. Зачем тому, что выглядит так, как будто оно читает имя файла (если это даже имеет смысл), брать массив и возвращать int - вызов его ReadFileName заставляет его звучать больше, как будто он ищет массив для имени файла и возвращает номер слота, в котором он был найден Здесь заканчивается фраза «назовите свои методы соответствующим образом 101»


Итак, другая идея заключалась в том, чтобы метод Read создавал свой собственный массив и возвращал его. Пока мы на нем, назовем его ReadFileNamed и попросим указать путь к файлу, чтобы его не было жестко закодировано для чтения только этого одного файла. И мы заставим его вернуть массив

static string[] ReadFileNamed(string filepath)
      ^^^^^^^^               ^^^^^^^^^^^^^^^
   the return type        the argument passed in

Сделайте так, чтобы первое, что он сделал, - объявил массив, достаточно большой для хранения файла (с этой идеей все еще есть проблемы, но это программирование 101; Я позволю им go. Не могу исправить все, используя то, чему вас еще не учили)

Поместите это где-нибудь разумно:

string lines = new string[10000];

И измените все ваши вхождения Вместо этого «name» будет «строками» - снова мы называем наши переменные точно так же, как мы разумно называем наши методы

Измените строку, которая читает фиксированное имя файла, чтобы использовать имя переменной, которое мы передаем ..

using (StreamReader file = new StreamReader(filepath))

В конце метода единственное, что остается сделать, это точно определить размер массива, прежде чем мы его вернем. Для 49-строчного файла счетчик будет 50, поэтому давайте сделаем массив размером 49, а затем заполним его, используя al oop (я сомневаюсь, что вам показали Array.Copy)

string[] toReturn = new string[counter-1];

for(int x = 0; x < toReturn.Length; x++)
  toReturn[x] = lines[x];

return toReturn;

И теперь назовите это так:

string[] fileLines = ReadFileNamed("student marks.txt");
0 голосов
/ 08 мая 2020

Вы определили свой метод для возврата string, но код внутри возвращает name, то есть string[]. Если вы хотите, чтобы он возвращал string[], измените подпись, указав, что:

static string[] ReadFileName(string[] name)

Однако, поскольку ваш метод только заполняет массив, который был передан, на самом деле нет необходимости возвращать array, поскольку у вызывающего уже есть ссылка на массив, который мы модифицируем (они передали его в первую очередь нашему методу).

Здесь есть потенциальная проблема, хотя
Мы ожидаем, что вызывающий объект передаст нам массив соответствующей длины для хранения всех допустимых строк из файла, но это число неизвестно, пока мы не прочитаем файл. Мы могли бы вернуть массив указанного размера либо с пустыми индексами в конце, если он был слишком большим, либо с неполными данными, если он был слишком мал, но вместо этого мы, вероятно, должны просто вернуть им массив новый , и не требовать от них передачи нам.

Обратите внимание, что проще использовать List<string> вместо string[], так как списки не требуют каких-либо сведений об их размере при создании экземпляра (они могут расти динамически). Кроме того, нам больше не нужна переменная counter (поскольку мы используем метод Add списка для добавления новых элементов), и мы можем удалить вызов file.Close(), поскольку блок using вызовет это автоматически (одна из замечательных вещей в них):

static string[] ReadFileName()
{
    List<string> validLines = new List<string>();

    using (StreamReader file = new StreamReader("StudentMarks.txt"))
    {
        string ln;

        while ((ln = file.ReadLine()) != null)
        {
            if (ln.Length > 4)
            {
                validLines.Add(ln);
            }
        }
    }

    return validLines.ToArray();
}

И мы можем еще больше упростить код, если будем использовать некоторые stati c методы класса System.IO.File:

static string[] ReadFileName()
{
    return File.ReadLines("StudentMarks.txt").Where(line => line.Length > 4).ToArray();
}

Мы также могли бы сделать метод немного более надежным, разрешив вызывающей стороне указывать имя файла, а также требования к минимальной длине строки:

static string[] ReadFileName(string fileName, int minLineLength)
{
    return File.ReadLines(fileName)
        .Where(line => line.Length >= minLineLength).ToArray();
}
0 голосов
/ 08 мая 2020

Ну, вы пытаетесь сделать несколько вещей одним методом:

  1. Прочитать "StudentMarks.txt" файл
  2. Поместить верхние строки в name существующие массив (что, если в файле слишком мало строк?)
  3. вернуть 50-й (маг c номер!) элемент

Если вы настаиваете на такая реализация:

using System.Linq;

...

static string readfileName(string[] name)
{
    var data = File
      .ReadLines("StudentMarks.txt")
      .Where(line => line.Length > 4)
      .Take(name.Length);

    int counter = 0; 

    foreach (item in data) 
      if (counter < name.Length)
        name[counter++] = item; 

    return name.Length > 50 ? name[50] : "";
}

Однако я предлагаю делать все отдельно:

 // Reading file lines, materialize them into string[] name
 string[] name = File
   .ReadLines("StudentMarks.txt")
   .Where(line => line.Length > 4)
  // .Take(51) // uncomment, if you want at most 51 items
   .ToArray();

 ...

 // 50th item of string[] name if any
 string item50 = name.Length > 50 ? name[50] : "";

Изменить: Разделение одиночная запись (name и score) в разных коллекций (name[] и score[]?) - часто плохая идея; сам критерий (line.Length > 4) тоже сомнительный (что, если у нас есть Lee - трехбуквенное имя - с оценкой 187?). Давайте реализуем F inite S tate M achine с 2 состояниями (когда мы читаем name или score) и читаем (name, score) пары:

    var data = File
      .ReadLines("StudentMarks.txt")
      .Select(line => line.Trim())
      .Where(line => !string.IsNullOrEmpty(line));

    List<(string name, int score)> namesAndScores = new List<(string name, int score)>();

    string currentName = null;

    foreach (string item in data) {
      if (null == currentName)
        currentName = item;
      else {
        namesAndScores.Add((currentName, int.Parse(item)));

        currentName = null;
      }
    }  

Теперь разобраться с namesAndScores:

  // 25th student and his/her score:
  if (namesAndScores.Count > 25)
    Console.Write($"{namesAndScores[25].name} achieve {namesAndScores[25].score}"); 
просто
0 голосов
/ 08 мая 2020

Если вы хотите вернуть имя [50] и знаете, что оно будет заполнено, почему бы не go с:

static string readfileName(string[] name)
    {

        using (StreamReader file = new StreamReader("StudentMarks.txt"))
        {
            int counter = 0;
            string ln;

            while ((ln = file.ReadLine()) != null)
            {
                if (ln.Length > 4)
                {
                    name[counter] = ln;
                    counter++;

                }
            }

            file.Close();
            return name[50];
        }
    }

Вы получаете сообщение об ошибке, потому что подпись вашего метода указывает, что вы собираетесь вернуть строку, но вы определяете имя как строку [] в аргументе. Если вы просто выберете один индекс вашего массива в операторе return, вы вернете только строку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...