跳至頁尾內容
USING IRONXL

如何在 C# 中將 CSV 文件讀取到 DataTable

在C#中將CSV文件轉換為DataTable,準備導入資料庫或資料操作。

.NET開發人員經常需要處理CSV文件,無論是導入銷售報告、處理庫存,還是同步客戶記錄。 將CSV文件轉換為DataTable使其易於操作、分析或插入到資料庫表中。 IronXL程式庫提供全功能的解決方案,用於在C#應用程式中處理Excel和CSV操作—不需要安裝Excel。

在C#中讀取CSV文件可能很棘手。 大型文件、不同的分隔符或嵌入的逗號通常需要複雜的解析邏輯。 IronXL簡化了這一點:只需幾行程式碼,您就可以從任何文件路徑中讀取CSV文件,將它們轉換為具有正確列標題的DataTable,並準備進行大量資料庫操作。 當在C#中處理Excel資料時,這種方法特別有用。

在本指南中,您將學習如何:

  • 使用IronXL的CSV讀取能力將CSV文件載入到C#中的DataTable
  • 處理不同的分隔符,如逗號、製表符或分號
  • 使用導出功能將您的DataTable高效地直接導入到SQL Server中
  • 可靠地管理大型資料集,不會遇到記憶體問題

最後,您將擁有一個完整且實用的工作流程,使用IronXL在您的.NET 10應用程式中將CSV資料轉變為可操作的資訊。


為什麼CSV轉換為DataTable至關重要?

CSV到DataTable的轉換讓開發人員可以將逗號分隔值轉換為結構化的物件以進一步處理。 無論您是在處理庫存資料、客戶記錄還是交易記錄,將CSV高效地轉換為DataTable都十分重要。 使用第一行作為列標題,您可以確保DataTable列與您的資料庫表模式對齊,這使其非常適合Excel到DataSet操作

傳統的方法經常在處理大型文件、不同的分隔符或記憶體管理方面遇到困難。 IronXL排除了這些挑戰,並能夠處理不同的分隔符、引號字段和嵌入的逗號,無需額外的程式碼。 IronXL的CSV讀取能力消除了常見的痛點,同時提供了額外的功能,如資料驗證和文件大小控制。

什麼時候應該選擇使用DataTable而非其他資料結構?

當您需要在記憶體中進行類似於資料庫的操作時,DataTables非常適合。 它們適用於涉及SQL Server導入、資料綁定到UI控件的場景,或者當您需要對結構化資料進行LINQ查詢時。 與簡單的陣列或列表不同,DataTables提供模式驗證、表間關係以及與ADO.NET的直接整合。 對於更複雜的場景,您還可以按需DataTable之間進行轉換

傳統的CSV解析存在哪些常見問題?

手動的CSV解析經常在邊緣情況下失效:在引用字段中嵌入逗號、單元格值中的換行符或不一致的分隔符使用。 當一次將所有內容載入到記憶體中時,記憶體消耗在處理大型文件時變得問題重重。 字元編碼問題可能會破壞國際資料,而型別推斷經常會錯誤地將數字字串解釋為數字。 這些挑戰為什麼結構化解析程式庫,如IronXL,對於生產應用變得至關重要。

如何避免大型CSV文件的記憶體問題?

IronXL實施了緩衝讀取技術來高效地處理大型CSV文件。 它不會一次將整個文件載入到記憶體中,而是分塊處理資料,甚至在面對大型文件時,也能保持小記憶體佔用。 這使其適合於資源有限的伺服器環境—包括雲部署,在這些環境中記憶體分配受到限制。


如何安裝IronXL?

開始使用IronXL只需要一個簡單的NuGet包安裝。 在Visual Studio中打開NuGet Package Manager Console,執行:

Install-Package IronXL.Excel

或者如果您更喜歡.NET CLI:

dotnet add package IronXL.Excel

安裝完成後,將IronXL命名空間新增到您的專案中:

using IronXL;
using System.Data;
using IronXL;
using System.Data;
Imports IronXL
Imports System.Data
$vbLabelText   $csharpLabel

這樣可以在無需Excel依賴的情況下存取所有的CSV處理功能。 更多詳情,請參考NuGet安裝指南

IronXL的系統要求是什麼?

IronXL支持.NET Framework 4.6.2+和.NET Core/5/6/7/8/9/10,可相容現代和過去的應用程式。 它可以在Windows、Linux和macOS平台上運行。 對於有特殊要求的環境,請查閱IronXL功能頁面來驗證平台支持。 您可以使用試用授權來評估所有的功能套件,再決定是否購買。

如何驗證安裝是否成功?

建立一個測試程式來載入CSV文件以驗證安裝是否成功。 如果遇到許可消息,您將需要獲取許可金鑰。 檢查專案文件中的包引用以確保IronXL以正確版本顯示。 進行基本的CSV讀取操作—如果它無錯誤完成,則您的安裝工作正常。


如何將CSV文件轉換為DataTable

IronXL的核心工作流程只需幾行程式碼。 以下是如何在C# 10+中使用頂層語句讀取CSV文件並將其轉換為DataTable

using IronXL;
using System.Data;

// Load CSV file into a WorkBook object
WorkBook workbook = WorkBook.LoadCSV("sales_data.csv",
    fileFormat: ExcelFileFormat.XLSX);

// Access the default worksheet
WorkSheet worksheet = workbook.DefaultWorkSheet;

// Convert to DataTable with headers
DataTable dataTable = worksheet.ToDataTable(true);

// Display the data
foreach (DataRow row in dataTable.Rows)
{
    foreach (var item in row.ItemArray)
    {
        Console.Write($"{item}\t");
    }
    Console.WriteLine();
}
using IronXL;
using System.Data;

// Load CSV file into a WorkBook object
WorkBook workbook = WorkBook.LoadCSV("sales_data.csv",
    fileFormat: ExcelFileFormat.XLSX);

// Access the default worksheet
WorkSheet worksheet = workbook.DefaultWorkSheet;

// Convert to DataTable with headers
DataTable dataTable = worksheet.ToDataTable(true);

// Display the data
foreach (DataRow row in dataTable.Rows)
{
    foreach (var item in row.ItemArray)
    {
        Console.Write($"{item}\t");
    }
    Console.WriteLine();
}
Imports IronXL
Imports System.Data

' Load CSV file into a WorkBook object
Dim workbook As WorkBook = WorkBook.LoadCSV("sales_data.csv", fileFormat:=ExcelFileFormat.XLSX)

' Access the default worksheet
Dim worksheet As WorkSheet = workbook.DefaultWorkSheet

' Convert to DataTable with headers
Dim dataTable As DataTable = worksheet.ToDataTable(True)

' Display the data
For Each row As DataRow In dataTable.Rows
    For Each item In row.ItemArray
        Console.Write($"{item}" & vbTab)
    Next
    Console.WriteLine()
Next
$vbLabelText   $csharpLabel

LoadCSV方法自動解析您的CSV文件,處理引用字段和嵌入的逗號。 fileFormat參數告知IronXL如何在內部解釋CSV資料,確保其被處理為與Excel相容的結構。 載入後,true指示第一行應用作為列標題。 這樣產生了一個清晰、結構化的DataTable,其命名列匹配您的CSV標題。 如需更多的高級操作選項,請參閱完整的IronXL使用指南

轉換過程也保持了資料型別——數字保持數值格式,日期解析正確,文字字段維持其格式。 這種自動型別推斷與手動解析方法相比,節省了大量的開發時間。 在需要自定義資料格式的情況下,您可以在轉換之前應用單元格格式

ToDataTable參數控制什麼?

ToDataTable()中的布林參數決定了標題行的處理。 設為Column1。 設false時,將分配通用欄名,這對於無標題的CSV文件很有用。 此靈活性支持在現實中遇到的各種CSV格式。 高級場景可能需要打開工作簿,以自定義選項處理多個資料範圍。

如何處理無標題的CSV文件?

對於無標題的CSV文件,請使用ToDataTable(false)並手動在之後分配欄位名稱。 您可以遍歷Columns集合,並根據您的資料模式設置有意義的名稱。 或者,可以在載入之前程式化地在CSV中預加標頭行。 IronXL還支持從頭建立Excel文件,如果您需要為現有資料新增標題,請建立

大CSV文件的性能影響是什麼?

IronXL使用緩衝技術高效處理大型CSV文件。 通常,100MB以下的文件可在數秒內載入。 由於智能緩衝,記憶體使用量可控,無論文件大小如何。 對於具有大量資料集的最佳性能,考慮使用範圍選擇技術進行分批處理。 在IronXL功能頁面詳細記錄了性能特性。


如何將DataTable導入到SQL Server中?

一旦擁有您的SqlBulkCopy將其高效導入到SQL Server中。 以下範例使用C#頂層語句:

using System.Data;
using Microsoft.Data.SqlClient;
using IronXL;

// Create connection string
string connectionString = "Data Source=localhost;Initial Catalog=SalesDB;" +
    "Integrated Security=True;TrustServerCertificate=True;";

// Read CSV into DataTable
WorkBook workbook = WorkBook.LoadCSV("inventory_report.csv");
DataTable dataTable = workbook.DefaultWorkSheet.ToDataTable(true);

// Bulk insert into SQL Server
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();

using SqlBulkCopy bulkCopy = new SqlBulkCopy(connection);

// Set destination table name
bulkCopy.DestinationTableName = "dbo.Inventory";

// Map DataTable columns to SQL table columns
bulkCopy.ColumnMappings.Add("ProductCode", "product_code");
bulkCopy.ColumnMappings.Add("Quantity", "quantity");
bulkCopy.ColumnMappings.Add("LastUpdated", "last_updated");

// Set batch size for better performance
bulkCopy.BatchSize = 1000;

// Write data to SQL Server
bulkCopy.WriteToServer(dataTable);

Console.WriteLine($"Successfully imported {dataTable.Rows.Count} records");
using System.Data;
using Microsoft.Data.SqlClient;
using IronXL;

// Create connection string
string connectionString = "Data Source=localhost;Initial Catalog=SalesDB;" +
    "Integrated Security=True;TrustServerCertificate=True;";

// Read CSV into DataTable
WorkBook workbook = WorkBook.LoadCSV("inventory_report.csv");
DataTable dataTable = workbook.DefaultWorkSheet.ToDataTable(true);

// Bulk insert into SQL Server
using SqlConnection connection = new SqlConnection(connectionString);
connection.Open();

using SqlBulkCopy bulkCopy = new SqlBulkCopy(connection);

// Set destination table name
bulkCopy.DestinationTableName = "dbo.Inventory";

// Map DataTable columns to SQL table columns
bulkCopy.ColumnMappings.Add("ProductCode", "product_code");
bulkCopy.ColumnMappings.Add("Quantity", "quantity");
bulkCopy.ColumnMappings.Add("LastUpdated", "last_updated");

// Set batch size for better performance
bulkCopy.BatchSize = 1000;

// Write data to SQL Server
bulkCopy.WriteToServer(dataTable);

Console.WriteLine($"Successfully imported {dataTable.Rows.Count} records");
Imports System.Data
Imports Microsoft.Data.SqlClient
Imports IronXL

' Create connection string
Dim connectionString As String = "Data Source=localhost;Initial Catalog=SalesDB;" &
    "Integrated Security=True;TrustServerCertificate=True;"

' Read CSV into DataTable
Dim workbook As WorkBook = WorkBook.LoadCSV("inventory_report.csv")
Dim dataTable As DataTable = workbook.DefaultWorkSheet.ToDataTable(True)

' Bulk insert into SQL Server
Using connection As New SqlConnection(connectionString)
    connection.Open()

    Using bulkCopy As New SqlBulkCopy(connection)
        ' Set destination table name
        bulkCopy.DestinationTableName = "dbo.Inventory"

        ' Map DataTable columns to SQL table columns
        bulkCopy.ColumnMappings.Add("ProductCode", "product_code")
        bulkCopy.ColumnMappings.Add("Quantity", "quantity")
        bulkCopy.ColumnMappings.Add("LastUpdated", "last_updated")

        ' Set batch size for better performance
        bulkCopy.BatchSize = 1000

        ' Write data to SQL Server
        bulkCopy.WriteToServer(dataTable)
    End Using
End Using

Console.WriteLine($"Successfully imported {dataTable.Rows.Count} records")
$vbLabelText   $csharpLabel

SqlBulkCopy類為大規模資料導入提供強大的性能。 DataTable列映射到具有不同名稱的資料庫列,確保資料模式的靈活性。 BatchSize屬性通過分塊處理記錄來優化記憶體使用,而非一次載入所有內容。 了解有關IronXL的導入和導出功能的更多資訊。

為了額外的資料完整性,考慮在批量複製操作周圍實施事務支持。 這確保如果出現錯誤,可以回滾部分導入。 您還可以使用IronXL的寫入Excel文件功能在導入之前預先驗證資料。

為什麼BatchSize對性能重要?

BatchSize控制在每次網路輪詢中發送到SQL Server的行數。較小的批次(100-1000行)減少記憶體使用並允許進度監控,但增加網路開銷。 較大的批次(5000-10000行)最大化吞吐量以適應快速網路,但需要更多記憶體。 最適合的大小取決於您的行寬、網路延遲和可用記憶體。

如何處理列映射不匹配?

列映射不匹配在批量複製操作期間會導致運行時異常。 始終驗證源DataTable列名稱完全匹配您的映射定義,包括大小寫敏感。 使用GetOrdinal方法來驗證映射之前的列存在性。 對於動態模式,查詢目標表的結構並程式化地建立映射。 IronXL的Excel至DataSet指南可以幫助在導入前標準化列名稱。

在批量導入中適用於哪些安全考量?

批量導入需要提高的資料庫許可,通常是bulkadmin角色。 盡可能使用整合身份驗證,以避免在連接字串中嵌入認證。 通過在導入期間新增審計欄來實施行級安全。 始終驗證資料型別和範圍,以防止由於格式錯誤的CSV資料而出現問題。 如果您需要了解企業部署要求,請查看IronXL許可頁面


如何處理不同的CSV分隔符?

不是所有CSV文件都使用逗號。 IronXL處理多種分隔符,這使其適用於國際資料格式或舊系統:

using IronXL;
using System;
using System.Data;
using System.IO;

// --- Tab-delimited file ---
string tsvPath = "export_data.tsv";
WorkBook tsvWorkbook = WorkBook.LoadCSV(tsvPath, ExcelFileFormat.XLSX, "\t");

// --- Semicolon-delimited file ---
string semiPath = "european_data.csv";
string tempCsv = Path.Combine(Path.GetTempPath(), "european_data_comma.csv");

// Replace semicolons with commas for proper parsing
string[] lines = File.ReadAllLines(semiPath);
for (int i = 0; i < lines.Length; i++)
{
    lines[i] = lines[i].Replace(';', ',');
}
File.WriteAllLines(tempCsv, lines);

WorkBook semiWorkbook = WorkBook.LoadCSV(tempCsv, ExcelFileFormat.XLSX);

// Print tab-delimited results
DataTable tsvTable = tsvWorkbook.DefaultWorkSheet.ToDataTable(true);
Console.WriteLine("--- Tab-delimited File ---");
foreach (DataColumn col in tsvTable.Columns)
    Console.Write($"{col.ColumnName}\t");
Console.WriteLine();
foreach (DataRow row in tsvTable.Rows)
{
    foreach (var item in row.ItemArray)
        Console.Write($"{item}\t");
    Console.WriteLine();
}

// Print semicolon-delimited results
DataTable semiTable = semiWorkbook.DefaultWorkSheet.ToDataTable(true);
Console.WriteLine("\n--- Semicolon-delimited File ---");
foreach (DataColumn col in semiTable.Columns)
    Console.Write($"{col.ColumnName}\t");
Console.WriteLine();
foreach (DataRow row in semiTable.Rows)
{
    foreach (var item in row.ItemArray)
        Console.Write($"{item}\t");
    Console.WriteLine();
}
using IronXL;
using System;
using System.Data;
using System.IO;

// --- Tab-delimited file ---
string tsvPath = "export_data.tsv";
WorkBook tsvWorkbook = WorkBook.LoadCSV(tsvPath, ExcelFileFormat.XLSX, "\t");

// --- Semicolon-delimited file ---
string semiPath = "european_data.csv";
string tempCsv = Path.Combine(Path.GetTempPath(), "european_data_comma.csv");

// Replace semicolons with commas for proper parsing
string[] lines = File.ReadAllLines(semiPath);
for (int i = 0; i < lines.Length; i++)
{
    lines[i] = lines[i].Replace(';', ',');
}
File.WriteAllLines(tempCsv, lines);

WorkBook semiWorkbook = WorkBook.LoadCSV(tempCsv, ExcelFileFormat.XLSX);

// Print tab-delimited results
DataTable tsvTable = tsvWorkbook.DefaultWorkSheet.ToDataTable(true);
Console.WriteLine("--- Tab-delimited File ---");
foreach (DataColumn col in tsvTable.Columns)
    Console.Write($"{col.ColumnName}\t");
Console.WriteLine();
foreach (DataRow row in tsvTable.Rows)
{
    foreach (var item in row.ItemArray)
        Console.Write($"{item}\t");
    Console.WriteLine();
}

// Print semicolon-delimited results
DataTable semiTable = semiWorkbook.DefaultWorkSheet.ToDataTable(true);
Console.WriteLine("\n--- Semicolon-delimited File ---");
foreach (DataColumn col in semiTable.Columns)
    Console.Write($"{col.ColumnName}\t");
Console.WriteLine();
foreach (DataRow row in semiTable.Rows)
{
    foreach (var item in row.ItemArray)
        Console.Write($"{item}\t");
    Console.WriteLine();
}
Imports IronXL
Imports System
Imports System.Data
Imports System.IO

' --- Tab-delimited file ---
Dim tsvPath As String = "export_data.tsv"
Dim tsvWorkbook As WorkBook = WorkBook.LoadCSV(tsvPath, ExcelFileFormat.XLSX, ControlChars.Tab)

' --- Semicolon-delimited file ---
Dim semiPath As String = "european_data.csv"
Dim tempCsv As String = Path.Combine(Path.GetTempPath(), "european_data_comma.csv")

' Replace semicolons with commas for proper parsing
Dim lines As String() = File.ReadAllLines(semiPath)
For i As Integer = 0 To lines.Length - 1
    lines(i) = lines(i).Replace(";"c, ","c)
Next
File.WriteAllLines(tempCsv, lines)

Dim semiWorkbook As WorkBook = WorkBook.LoadCSV(tempCsv, ExcelFileFormat.XLSX)

' Print tab-delimited results
Dim tsvTable As DataTable = tsvWorkbook.DefaultWorkSheet.ToDataTable(True)
Console.WriteLine("--- Tab-delimited File ---")
For Each col As DataColumn In tsvTable.Columns
    Console.Write($"{col.ColumnName}{ControlChars.Tab}")
Next
Console.WriteLine()
For Each row As DataRow In tsvTable.Rows
    For Each item In row.ItemArray
        Console.Write($"{item}{ControlChars.Tab}")
    Next
    Console.WriteLine()
Next

' Print semicolon-delimited results
Dim semiTable As DataTable = semiWorkbook.DefaultWorkSheet.ToDataTable(True)
Console.WriteLine(vbCrLf & "--- Semicolon-delimited File ---")
For Each col As DataColumn In semiTable.Columns
    Console.Write($"{col.ColumnName}{ControlChars.Tab}")
Next
Console.WriteLine()
For Each row As DataRow In semiTable.Rows
    For Each item In row.ItemArray
        Console.Write($"{item}{ControlChars.Tab}")
    Next
    Console.WriteLine()
Next
$vbLabelText   $csharpLabel

此程式碼展示了如何載入具有不同分隔符的CSV文件到IronXL WorkBook物件中。 帶製表符分隔的文件可使用"\t"作為分隔符讀取,而帶分號分隔的文件在載入之前轉換為標準CSV格式。 對於更複雜的文件格式情景,您也可以探索將Excel資料導出到各種格式。

哪個分隔符是IronXL原生支持的?

IronXL支持常見的分隔符,包括逗號、製表符、管道(|), and custom single-character delimiters through the LoadCSV 重載。 為獲得最佳性能,請使用內建的分隔符參數,而不是預處理文件。 多字元分隔符需要預處理,如上面的分號範例所示。 您還可以在生成的工作簿中合併單元格,如果您需要重新組織解析資料。

如何自動檢測分隔符?

自動分隔符檢測涉及分析CSV文件的前幾行。統計每行中常見分隔符(逗號、製表符、分號、管道)的出現次數。出現頻率最高且一致的字元很可能就是分隔符。 IronXL不提供自動檢測,但您可以在調用LoadCSV之前實現此邏輯。


IronXL支持的功能超越CSV有哪些?

IronXL不僅僅涵蓋CSV讀取。 該程式庫在C#中處理Excel和表格操作的全過程,成為資料中心應用程式的單一依賴。

功能 描述 詳細了解
讀取Excel文件 載入.xlsx、.xls、.csv文件並以程式化方式存取單元格資料 Read Excel
寫入Excel文件 建立和修改電子表格,設置值、公式和樣式 Write Excel
建立Excel文件 從程式碼生成新的工作簿和工作表 Create Excel
導出到DataSet 將整個工作簿轉換為DataSet物件,以用於多表格場景 Excel to DataSet
單元格格式設置 將數字格式、字體、顏色和邊框應用於單元格 單元格格式設置
導入資料 從DataTable、List或資料庫源填充工作表 Import Data

如何將整個工作簿導出為DataSet?

當您的CSV文件包含多個部分或需要處理多個工作表時,請在ToDataSet()方法。 這將返回一個DataTableExcel至DataSet如何做提供了這個情景的逐步程式碼範例。

有什麼許可選擇?

IronXL可用於商業用途,有多種許可級別以滿足您的團隊規模和部署需求。 免費試用許可提供對完整功能套件的評估存取權限。 許可頁面記錄了包括免版稅再分發和SaaS選項的可用計劃。


下一步是什麼?

IronXL將CSV資料庫導入過程轉變為簡單且可靠的流程。 只需幾行程式碼,您就可以:

  • 使用LoadCSV功能從任何文件路徑中讀取CSV文件
  • 將CSV資料轉換為DataTable,具有適當的資料格式
  • 通過自動解析保留列標題和DataTable列
  • 使用批量操作高效地導入數百萬行到資料庫表中

該程式庫處理CSV解析、記憶體管理和資料型別轉換的細節,讓您專注於業務邏輯而不是文件處理細節。 無論您是在構建ASP.NET應用程式、使用Blazor,還是開發.NET 10桌面應用程式,IronXL在所有平台上提供一致可靠的CSV處理。

準備好開始了嗎? IronXL文件全面涵蓋每個功能,試用許可讓您可以評估整個程式庫。

常見問題

在C#中將CSV檔案讀取至DataTable的最佳方法是什麼?

使用IronXL,您可以高效地在C#中將CSV檔案讀取至DataTable,我們的開發者指南提供了簡單的程式碼範例。

為什麼我應該使用IronXL進行CSV到DataTable的轉換?

IronXL提供簡單的API來解析CSV檔案並將其轉換為DataTable,讓您可以輕鬆地在C#中操作和分析資料。

IronXL能處理大型CSV檔案轉換為DataTable嗎?

是的,IronXL旨在高效處理大型CSV檔案並將其轉換為DataTable,且不會出現效能問題。

IronXL是否支持在CSV轉換為DataTable後的資料操作?

絕對可以,一旦您使用IronXL將CSV檔案轉換為DataTable,您可以輕鬆地根據需要操作和分析資料。

如何使用IronXL將CSV資料匯入資料庫?

在使用IronXL將CSV檔案轉換為DataTable後,您可以使用C#中的標準資料庫連接將資料插入到資料庫中。

IronXL適合用於企業應用程式中的CSV處理嗎?

是的,IronXL被設計用於處理企業應用程式中的CSV任務,提供穩健的效能和可靠性。

將CSV檔案轉換為C#中的DataTable有什麼優勢?

將CSV檔案轉換為DataTable允許更易於操作、分析和與資料庫整合,提升您的應用程式資料處理能力。

IronXL可以用於CSV以外的其他試算表檔案型別嗎?

是的,IronXL支援各種試算表檔案型別,包括Excel格式,提供在C#中的多功能資料處理能力。

Curtis Chau
技術作家

Curtis Chau擁有Carleton大學的電腦科學學士學位,專精於前端開發,擁有Node.js、TypeScript、JavaScript和React的專業知識。Curtis熱衷於建立直觀且美觀的使用者介面,喜愛使用現代框架並建立結構良好、視覺吸引力的手冊。

除了開發,Curtis對物聯網(IoT)有濃厚的興趣,探索創新的方法來整合硬體和軟體。在空閒時間,他喜歡玩遊戲和建立Discord機器人,結合他對技術的熱愛與創造力。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話