# C# Coding Conventions

> Coding standards, base classes, lifecycle rules, and patterns for Kingdee Enterprise C# development.

---

## Scope

Use this guide for Enterprise/K3 Cloud C# plugin and service code. Always compare with existing project examples because Kingdee SDK versions and base class names can differ by product line.

If the base class or event name cannot be verified from project references or existing code, stop and ask instead of inventing a platform API.

---

## Base Class Selection

| Scenario | Common base class family | Notes |
| --- | --- | --- |
| Dynamic form plugin | `AbstractDynamicFormPlugIn` / local derived base | UI events, control state, model interaction |
| Bill form plugin | `AbstractBillPlugIn` / local derived base | Bill fields, entries, toolbar/menu events |
| List plugin | list-view plugin base used by project | List filters, toolbar actions, selected rows |
| Operation service plugin | operation/service plugin base used by project | Save/submit/audit/close business operations |
| Validator | validator base/interface used by operation pipeline | Business validation, not UI state |
| Web/API service | controller/service base used by project | Contract validation, authorization, DTO mapping |

Rules:

- Prefer the narrowest existing base class in the target project.
- Prefer local project base classes when they encode logging, context, permission, or exception conventions.
- Do not mix UI-only APIs into operation service plugins.
- Do not add a new base class unless at least two concrete plugins share non-trivial behavior and lifecycle constraints are identical.

---

## Naming Conventions

| Element | Convention | Example |
| --- | --- | --- |
| Namespace | Company.Product.Module.Layer | `Kingdee.Fin.Pay.Plugin` |
| Class | UpperCamelCase | `PayBillPlugin` |
| Interface | `I` prefix | `IPaySyncService` |
| Method/variable | UpperCamelCase for methods/properties, lowerCamelCase for locals | `BuildRequest`, `billId` |
| Constants | Local project style; prefer `const`/`static readonly` with clear names | `SubmitOperationKey` |
| Form plugin | `{Name}FormPlugin` | `PayFormPlugin` |
| Bill plugin | `{Name}BillPlugin` | `PayBillPlugin` |
| List plugin | `{Name}ListPlugin` | `PayListPlugin` |
| Operation plugin | `{Operation}OperationPlugin` | `SubmitOperationPlugin` |
| Validator | `{Rule}Validator` | `AmountLimitValidator` |

---

## Project Structure

Prefer the target project's existing layout. If none exists, use a small domain-oriented structure:

```text
src/
  <Module>.Abstractions/     # DTOs, constants, interfaces
  <Module>.Services/         # business services and integration clients
  <Module>.Plugins/          # form/bill/list/operation plugins
  <Module>.Tests/            # unit and integration tests
```

Keep plugin classes thin:

1. Read context/model values.
2. Validate obvious UI/input preconditions.
3. Call domain service/helper.
4. Apply UI feedback or operation result.

---

## Metadata and DynamicObject Rules

- Verify field keys, form IDs, operation keys, enum values, and entry entity keys before using them.
- Prefer constants for repeated field keys and operation keys.
- Null-check every optional dynamic object and entry row before reading nested values.
- Avoid stringly typed deep chains scattered across code; isolate mapping in one helper when repeated.
- Do not infer stable identifiers from localized captions.

---

## Lifecycle Rules

| Lifecycle area | Allowed | Avoid |
| --- | --- | --- |
| Initialization | Register lightweight dependencies and listeners | Heavy DB/API calls |
| Data binding | Set UI state based on loaded data | Mutating persisted data without explicit event reason |
| Field change | Recalculate dependent fields with equality checks | Unconditional set-value loops |
| Toolbar/button click | Validate selection and dispatch service call | Large business logic directly in plugin |
| Operation service | Validate/modify operation data in transaction-aware flow | Calling UI view/model APIs |

---

## Error Handling

| Situation | Rule |
| --- | --- |
| User/business validation | Return platform business message or validation result |
| Transaction consistency risk | Throw platform/business exception so operation rolls back |
| External dependency failure | Log safe context; return controlled business failure unless async compensation is designed |
| Unexpected exception | Preserve original exception as cause/inner exception and show sanitized user message |

Forbidden:

- Empty `catch` blocks.
- Throwing generic exceptions without context for expected business cases.
- Exposing stack traces, SQL, secrets, or full payloads to users.
- Continuing a save/submit operation after a required relation or base data record is missing.

---

## Data and Performance Rules

- Batch load base data and related records; avoid DB/API calls inside row loops.
- Use project data access helpers instead of ad-hoc SQL when they exist.
- Parameterize all raw SQL.
- For list pages, paginate and filter server-side.
- Treat attachments and large text fields as expensive; load lazily when possible.
- Make operation plugins idempotent when users may retry after timeout.

---

## Review Checklist

- [ ] Correct platform and base class verified from existing code/references.
- [ ] Plugin lifecycle methods are used for their intended timing.
- [ ] Business logic is not trapped in UI event handlers.
- [ ] Field keys and operation keys are constants or verified literals.
- [ ] Null/dynamic object access is guarded.
- [ ] Transactions fail fast on consistency errors.
- [ ] No raw SQL string concatenation.
- [ ] Build/test command has been run or limitation is reported.

---

## Minimal Examples

These examples show the expected shape only. For complete templates and SDK-specific snippets, use the `kd-enterprise-csharp` skill assets.

### Thin Bill Plugin Shape

```csharp
public sealed class PayBillPlugin : AbstractBillPlugIn
{
    private const string AmountFieldKey = "FAmount";
    private readonly IPayService payService;

    public override void DataChanged(DataChangedEventArgs e)
    {
        base.DataChanged(e);
        if (e.Field.Key != AmountFieldKey)
        {
            return;
        }

        var amount = this.Model.GetValue(AmountFieldKey);
        this.payService.Recalculate(amount);
    }
}
```

### Service Boundary Shape

```csharp
public sealed class PayService
{
    public PayResult Submit(PayRequest request)
    {
        if (request == null)
        {
            return PayResult.Fail("请求不能为空。");
        }

        // Validate business invariants before calling Kingdee operation APIs.
        return PayResult.Success();
    }
}
```

