C#: تدريب: LINQ والملفات

1. محلل درجات الطلاب

تحويل بيانات الطلاب إلى ملف JSON، ثم استرجاعها واستخدام LINQ لإجراء تحليل إحصائي متعدد الأبعاد: متوسط الدرجات، وأفضل ثلاثة طلاب، وعدد الناجحين، والتجميع حسب المستوى الدراسي.

(1) المتطلبات

▶ مثال

CSHARP 📖 للعرض فقط
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;

public class Student
{
    public string Name { get; set; }
    public int Score { get; set; }
}

class Program
{
    static void Main()
    {
        var students = new List<Student>
        {
            new Student { Name = "Zhang San", Score = 92 },
            new Student { Name = "Li Si", Score = 45 },
            new Student { Name = "Wang Wu", Score = 78 },
            new Student { Name = "Zhao Liu", Score = 88 },
            new Student { Name = "Qian Qi", Score = 55 },
            new Student { Name = "Sun Ba", Score = 95 },
            new Student { Name = "Zhou Jiu", Score = 33 },
            new Student { Name = "Wu Shi", Score = 71 }
        };

        string tempDir = Path.GetTempPath();
        string filePath = Path.Combine(tempDir, "students.json");

        var options = new JsonSerializerOptions { WriteIndented = true };
        string json = JsonSerializer.Serialize(students, options);
        File.WriteAllText(filePath, json);
        Console.WriteLine("Written to: " + filePath);

        string readJson = File.ReadAllText(filePath);
        var loaded = JsonSerializer.Deserialize<List<Student>>(readJson);

        double average = loaded.Average(s => s.Score);
        Console.WriteLine($"Average score: {average:F1}");

        var top3 = loaded.OrderByDescending(s => s.Score).Take(3);
        Console.WriteLine("Top three:");
        foreach (var s in top3)
        {
            Console.WriteLine($"  {s.Name} - {s.Score}");
        }

        int passCount = loaded.Count(s => s.Score >= 60);
        Console.WriteLine($"Pass count: {passCount}");

        var grouped = loaded.GroupBy(s => s.Score >= 90 ? "Excellent"
            : s.Score >= 60 ? "Pass" : "Fail");
        Console.WriteLine("Grade groups:");
        foreach (var group in grouped)
        {
            Console.WriteLine($"  {group.Key}: {string.Join(", ", group.Select(s => s.Name))}");
        }

        File.Delete(filePath);
    }
}
53 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
TEXT 📖 للعرض فقط
Written to: /tmp/students.json
Average score: 69.6
Top three:
  Sun Ba - 95
  Zhang San - 92
  Zhao Liu - 88
Pass count: 5
Grade groups:
  Excellent: Zhang San, Sun Ba
  Fail: Li Si, Qian Qi, Zhou Jiu
  Pass: Wang Wu, Zhao Liu, Wu Shi

2. محلل السجلات البسيط

قراءة ملف سجل نصي، وتحليل كل سطر، واستخدام LINQ لحساب عدد الأخطاء/التحذيرات، وتحديد الخطأ الأكثر تكرارًا، والتصفية حسب النطاق الزمني.

(1) المتطلبات

▶ مثال

CSHARP 📖 للعرض فقط
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        string tempDir = Path.GetTempPath();
        string logPath = Path.Combine(tempDir, "app.log");

        var lines = new List```<string>```
        {
            "[2025-01-10] [INFO] System started",
            "[2025-01-10] [WARN] Memory usage 80%",
            "[2025-01-11] [ERROR] Database connection failed",
            "[2025-01-11] [ERROR] Disk full",
            "[2025-01-12] [INFO] User login",
            "[2025-01-12] [ERROR] Database connection failed",
            "[2025-01-13] [WARN] CPU usage 90%",
            "[2025-01-13] [ERROR] Network timeout",
            "[2025-01-14] [INFO] Scheduled task completed",
            "[2025-01-14] [ERROR] Database connection failed"
        };
        File.WriteAllLines(logPath, lines);

        var logEntries = File.ReadAllLines(logPath)
            .Select(line =>
            {
                var parts = line.Split(']');
                return new
                {
                    Date = parts[0].TrimStart('[').Trim(),
                    Level = parts[1].TrimStart('[').Trim(),
                    Message = parts[2].Trim()
                };
            })
            .ToList();

        var levelCounts = logEntries
            .GroupBy(e => e.Level)
            .Select(g => new { Level = g.Key, Count = g.Count() })
            .OrderByDescending(x => x.Count);
        Console.WriteLine("Level counts:");
        foreach (var item in levelCounts)
        {
            Console.WriteLine($"  {item.Level}: {item.Count}");
        }

        var topError = logEntries
            .Where(e => e.Level == "ERROR")
            .GroupBy(e => e.Message)
            .OrderByDescending(g => g.Count())
            .First();
        Console.WriteLine($"Most frequent error: {topError.Key} ({topError.Count()} times)");

        var filtered = logEntries
            .Where(e => string.Compare(e.Date, "2025-01-11") >= 0
                     && string.Compare(e.Date, "2025-01-13") <= 0)
            .ToList();
        Console.WriteLine("Logs from Jan 11-13:");
        foreach (var entry in filtered)
        {
            Console.WriteLine($"  [{entry.Date}] [{entry.Level}] {entry.Message}");
        }

        File.Delete(logPath);
    }
}
63 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
TEXT 📖 للعرض فقط
Level counts:
  ERROR: 5
  INFO: 3
  WARN: 2
Most frequent error: Database connection failed (3 times)
Logs from Jan 11-13:
  [2025-01-11] [ERROR] Database connection failed
  [2025-01-11] [ERROR] Disk full
  [2025-01-12] [INFO] User login
  [2025-01-12] [ERROR] Database connection failed
  [2025-01-13] [WARN] CPU usage 90%
  [2025-01-13] [ERROR] Network timeout

3. أداة إعادة تسمية الملفات دفعة واحدة

سرد الملفات الموجودة في أحد الدلائل، وتصفيتها حسب الامتداد باستخدام LINQ، ثم إضافة بادئة أو لاحقة بشكل جماعي.

(1) المتطلبات

▶ مثال

CSHARP 📖 للعرض فقط
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        string workDir = Path.Combine(Path.GetTempPath(), "rename_demo");
        Directory.CreateDirectory(workDir);

        var demoFiles = new[] { "photo1.jpg", "photo2.jpg", "doc.txt", "notes.txt", "data.csv" };
        foreach (var f in demoFiles)
        {
            File.WriteAllText(Path.Combine(workDir, f), "demo");
        }
        Console.WriteLine("Original files:");
        foreach (var f in Directory.GetFiles(workDir))
        {
            Console.WriteLine("  " + Path.GetFileName(f));
        }

        string targetExt = ".txt";
        string prefix = "backup_";

        var filesToRename = Directory.GetFiles(workDir)
            .Where(f => Path.GetExtension(f).Equals(targetExt, StringComparison.OrdinalIgnoreCase))
            .ToList();

        Console.WriteLine($"\nFiltering extension {targetExt}:");
        foreach (var f in filesToRename)
        {
            string dir = Path.GetDirectoryName(f);
            string nameNoExt = Path.GetFileNameWithoutExtension(f);
            string newName = prefix + nameNoExt + targetExt;
            string newPath = Path.Combine(dir, newName);
            Console.WriteLine($"  {Path.GetFileName(f)} -> {newName}");
            File.Move(f, newPath);
        }

        Console.WriteLine("\nAfter renaming:");
        foreach (var f in Directory.GetFiles(workDir))
        {
            Console.WriteLine("  " + Path.GetFileName(f));
        }

        Directory.Delete(workDir, true);
    }
}
43 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
TEXT 📖 للعرض فقط
Original files:
  photo1.jpg
  photo2.jpg
  doc.txt
  notes.txt
  data.csv

Filtering extension .txt:
  doc.txt -> backup_doc.txt
  notes.txt -> backup_notes.txt

After renaming:
  photo1.jpg
  photo2.jpg
  backup_doc.txt
  backup_notes.txt
  data.csv

❓ أسئلة شائعة

س ماذا لو لم تتطابق أسماء الخصائص أثناء عملية إزالة التسلسل في JSON؟
ج استخدم السمة [JsonPropertyName] لتعيين الأسماء، أو قم بتخصيص PropertyNamingPolicy في JsonSerializerOptions.
س هل ترتيب نتائج GroupBy مضمون؟
ج لا، لا تضمن GroupBy ترتيب المجموعات. استخدم OrderBy للفرز قبل إجراء عملية التكرار.
س ماذا يحدث إذا كان الملف المستهدف موجودًا بالفعل عند استخدام File.Move لإعادة تسميته؟
ج يتم إلقاء استثناء IOException. يجب عليك أولاً التحقق من وجود المسار المستهدف، أو استخدام File.Replace.
س هل تُعدّ دالة ReadAllLines مناسبة لقراءة ملفات السجلات الكبيرة؟
ج لا، حيث تقوم دالة ReadAllLines بتحميل المحتوى بالكامل إلى الذاكرة دفعة واحدة. بالنسبة للملفات الكبيرة، استخدم دالة File.ReadLines للقراءة المتأخرة، سطراً سطراً.

📖 ملخص

📝 تمارين

  1. توسيع نطاق أداة تحليل درجات الطلاب بحيث تقوم بحساب عدد الطلاب حسب نطاقات الدرجات (0-59/60-79/80-89/90-100) وعرض مخطط شريطي (باستخدام أحرف *)
  2. إضافة ميزة إلى محلل السجلات: حساب عدد إدخالات «ERROR» يوميًا وعرض النطاق الزمني الذي شهد أكبر عدد من حالات «ERROR» المتتالية
  3. إعادة هيكلة أداة إعادة تسمية الملفات بحيث تقبل معلمات سطر الأوامر الخاصة بمسار الدليل، وفلتر الامتداد، والبادئة، وإضافة وضع --dry-run الذي يعرض المعاينة فقط دون تنفيذ العملية
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%