在ASP.NET MVC裡定義了一個擴充方法(Extension Method),打算在CSHTML中使用:(以下擴充方法為脫褲子放屁,純屬示範,為String新増一個GetLength()方法傳回字串長度)

namespace BBDPWeb.Models
{
    public static class ExtMethodDemo
    {
        public static int GetLength(this string s)
        {
            return s.Length;
        }
    }
}

在Action中,使用Title存入標題字串放入ViewBag,在CSHTML則加入using BBDPWeb.Models,確保引用ExtMethoDemo所在命名空間啟用擴充方法,既然Title為String,理論上ViewBag.Title.GetLength()應可取得字串長度。

@using BBDPWeb.Models
 
<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title.GetLength() - BBDP</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    <link href="~/Content/kendo/kendo.common.min.css" rel="stylesheet" />
    <link href="~/Content/kendo/kendo.afet.min.css" rel="stylesheet" />

錯!CSHTML彈出錯誤訊息,指出Title沒有GetLength()這個方法。

一開始猛往「沒正確引用命名空間」方向追查,繞了半圈才發現問題出在ViewBag上。

ViewBag的型別為dynamic,故編譯階段難以判別ViewBag.Title型別為何,無法確認其為String就不會套用String專屬的擴充方法。

stackoverflow查到一段說明:(原PO曾為微軟RD)

In regular, non-dynamic code extension methods work by doing a full search of all the classes known to the compiler for a static class that has an extension method that match. The search goes in order based on the namespace nesting and available "using" directives in each namespace.

That means that in order to get a dynamic extension method invocation resolved correctly, somehow the DLR has to know at runtime what all the namespace nestings and "using" directives were in your source code. We do not have a mechanism handy for encoding all that information into the call site. We considered inventing such a mechanism, but decided that it was too high cost and produced too much schedule risk to be worth it.

簡單地說,動態型別要使用擴充函式,必須在執行期間搜索所有關聯的命名空間,難度極高,故.NET Team未將其納入內建功能。要在動態物件(不限於MVC CSHTML,任何用到dynamic的地方都是如此)使用擴充函式,必須加上強型別宣告。

知道原委,將程式修改為((string)ViewBag.Title).GetLength(),就能正確呼叫擴充方法囉~

@using BBDPWeb.Models
 
<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@(((string)ViewBag.Title).GetLength()) - BBDP</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    <link href="~/Content/kendo/kendo.common.min.css" rel="stylesheet" />
    <link href="~/Content/kendo/kendo.afet.min.css" rel="stylesheet" />
 

Comments

Be the first to post a comment

Post a comment