C#: تدريب: LINQ والملفات
1. محلل درجات الطلاب
تحويل بيانات الطلاب إلى ملف JSON، ثم استرجاعها واستخدام LINQ لإجراء تحليل إحصائي متعدد الأبعاد: متوسط الدرجات، وأفضل ثلاثة طلاب، وعدد الناجحين، والتجميع حسب المستوى الدراسي.
(1) المتطلبات
- تعريف فئة
Studentتحتوي على الخصائصNameوScore - تحويل قائمة الطلاب إلى تنسيق JSON وحفظها في ملف مؤقت
- قراءة البيانات من الملف وإزالة التسلسل
- استخدم LINQ لحساب: متوسط الدرجات، وأفضل ثلاثة طلاب، وعدد الطلاب الذين اجتازوا الاختبار (60 أو أكثر)، والتجميع حسب المستوى الدراسي
▶ مثال
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);
}
}
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) المتطلبات
- تنسيق السجل:
[Date] [Level] Message، على سبيل المثال[2025-01-15] [ERROR] Disk full - حساب عدد الإدخالات حسب المستوى
- ابحث عن رسالة الخطأ الأكثر تكرارًا
- تصفية السجلات حسب النطاق الزمني
▶ مثال
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);
}
}
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);
}
}
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 للقراءة المتأخرة، سطراً سطراً.
📖 ملخص
- تطبيق LINQ وتسلسل JSON بشكل شامل من أجل حفظ البيانات والتحليل الإحصائي
- إتقان تحليل السجلات، والتجميع والتجميع باستخدام LINQ، وتقنيات تصفية البيانات حسب التاريخ
- استخدام فئتي «Directory» و«Path» بالاقتران مع LINQ لتصفية الملفات والعمليات الدفعية
- تستخدم جميع أمثلة عمليات الملفات الدالة Path.GetTempPath() لضمان التوافق عبر الأنظمة الأساسية المختلفة
- يتم مسح الملفات والمجلدات المؤقتة بعد استخدامها لتجنب بقاء أي آثار
📝 تمارين
- توسيع نطاق أداة تحليل درجات الطلاب بحيث تقوم بحساب عدد الطلاب حسب نطاقات الدرجات (0-59/60-79/80-89/90-100) وعرض مخطط شريطي (باستخدام أحرف
*) - إضافة ميزة إلى محلل السجلات: حساب عدد إدخالات «ERROR» يوميًا وعرض النطاق الزمني الذي شهد أكبر عدد من حالات «ERROR» المتتالية
- إعادة هيكلة أداة إعادة تسمية الملفات بحيث تقبل معلمات سطر الأوامر الخاصة بمسار الدليل، وفلتر الامتداد، والبادئة، وإضافة وضع
--dry-runالذي يعرض المعاينة فقط دون تنفيذ العملية